Merge branch 'main' into production

This commit is contained in:
Mouse Reeve 2021-01-11 15:29:50 -08:00
commit 12dbd47207
22 changed files with 529 additions and 63 deletions

View file

@ -41,6 +41,7 @@ class Edition(Book):
pages: int = None
physicalFormat: str = ''
publishers: List[str] = field(default_factory=lambda: [])
editionRank: int = 0
type: str = 'Edition'

View file

@ -7,7 +7,11 @@ class Connector(AbstractMinimalConnector):
''' this is basically just for search '''
def get_or_create_book(self, remote_id):
return activitypub.resolve_remote_id(models.Edition, remote_id)
edition = activitypub.resolve_remote_id(models.Edition, remote_id)
work = edition.parent_work
work.default_edition = work.get_default_edition()
work.save()
return edition
def parse_search_data(self, data):
return data

View file

@ -0,0 +1,27 @@
# Generated by Django 3.0.7 on 2021-01-11 17:18
import bookwyrm.models.fields
from django.db import migrations
def set_rank(app_registry, schema_editor):
db_alias = schema_editor.connection.alias
books = app_registry.get_model('bookwyrm', 'Edition')
for book in books.objects.using(db_alias):
book.edition_rank = book.get_rank
book.save()
class Migration(migrations.Migration):
dependencies = [
('bookwyrm', '0034_importjob_complete'),
]
operations = [
migrations.AddField(
model_name='edition',
name='edition_rank',
field=bookwyrm.models.fields.IntegerField(default=0),
),
migrations.RunPython(set_rank),
]

View file

@ -52,10 +52,11 @@ def execute_after_save(sender, instance, created, *args, **kwargs):
instance.save()
def unfurl_related_field(related_field):
def unfurl_related_field(related_field, sort_field=None):
''' load reverse lookups (like public key owner or Status attachment '''
if hasattr(related_field, 'all'):
return [unfurl_related_field(i) for i in related_field.all()]
return [unfurl_related_field(i) for i in related_field.order_by(
sort_field).all()]
if related_field.reverse_unfurl:
return related_field.field_to_activity()
return related_field.remote_id
@ -145,11 +146,11 @@ class ActivitypubMixin:
if hasattr(self, 'serialize_reverse_fields'):
# for example, editions of a work
for model_field_name, activity_field_name in \
for model_field_name, activity_field_name, sort_field in \
self.serialize_reverse_fields:
related_field = getattr(self, model_field_name)
activity[activity_field_name] = \
unfurl_related_field(related_field)
unfurl_related_field(related_field, sort_field)
if not activity.get('id'):
activity['id'] = self.get_remote_id()

View file

@ -122,20 +122,29 @@ class Work(OrderedCollectionPageMixin, Book):
load_remote=False
)
def save(self, *args, **kwargs):
''' set some fields on the edition object '''
# set rank
for edition in self.editions.all():
edition.save()
return super().save(*args, **kwargs)
def get_default_edition(self):
''' in case the default edition is not set '''
return self.default_edition or self.editions.first()
return self.default_edition or self.editions.order_by(
'-edition_rank'
).first()
def to_edition_list(self, **kwargs):
''' an ordered collection of editions '''
return self.to_ordered_collection(
self.editions.order_by('-updated_date').all(),
self.editions.order_by('-edition_rank').all(),
remote_id='%s/editions' % self.remote_id,
**kwargs
)
activity_serializer = activitypub.Work
serialize_reverse_fields = [('editions', 'editions')]
serialize_reverse_fields = [('editions', 'editions', '-edition_rank')]
deserialize_reverse_fields = [('editions', 'editions')]
@ -164,17 +173,39 @@ class Edition(Book):
parent_work = fields.ForeignKey(
'Work', on_delete=models.PROTECT, null=True,
related_name='editions', activitypub_field='work')
edition_rank = fields.IntegerField(default=0)
activity_serializer = activitypub.Edition
name_field = 'title'
@property
def get_rank(self):
''' calculate how complete the data is on this edition '''
if self.parent_work and self.parent_work.default_edition == self:
# default edition has the highest rank
return 20
rank = 0
rank += int(bool(self.cover)) * 3
rank += int(bool(self.isbn_13))
rank += int(bool(self.isbn_10))
rank += int(bool(self.oclc_number))
rank += int(bool(self.pages))
rank += int(bool(self.physical_format))
rank += int(bool(self.description))
# max rank is 9
return rank
def save(self, *args, **kwargs):
''' calculate isbn 10/13 '''
''' set some fields on the edition object '''
# calculate isbn 10/13
if self.isbn_13 and self.isbn_13[:3] == '978' and not self.isbn_10:
self.isbn_10 = isbn_13_to_10(self.isbn_13)
if self.isbn_10 and not self.isbn_13:
self.isbn_13 = isbn_10_to_13(self.isbn_10)
# set rank
self.edition_rank = self.get_rank
return super().save(*args, **kwargs)

View file

@ -39,7 +39,7 @@ class Shelf(OrderedCollectionMixin, BookWyrmModel):
@property
def collection_queryset(self):
''' list of books for this shelf, overrides OrderedCollectionMixin '''
return self.books
return self.books.all()
def get_remote_id(self):
''' shelf identifier instead of id '''

View file

@ -47,7 +47,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
objects = InheritanceManager()
activity_serializer = activitypub.Note
serialize_reverse_fields = [('attachments', 'attachment')]
serialize_reverse_fields = [('attachments', 'attachment', 'id')]
deserialize_reverse_fields = [('attachments', 'attachment')]
@classmethod

View file

@ -200,7 +200,7 @@ class KeyPair(ActivitypubMixin, BookWyrmModel):
blank=True, null=True, activitypub_field='publicKeyPem')
activity_serializer = activitypub.PublicKey
serialize_reverse_fields = [('owner', 'owner')]
serialize_reverse_fields = [('owner', 'owner', 'id')]
def get_remote_id(self):
# self.owner is set by the OneToOneField on User

View file

@ -180,6 +180,13 @@ def handle_imported_book(user, item, include_reviews, privacy):
broadcast(user, shelf_book.to_add_activity(user), privacy=privacy)
for read in item.reads:
# check for an existing readthrough with the same dates
if models.ReadThrough.objects.filter(
user=user, book=item.book,
start_date=read.start_date,
finish_date=read.finish_date
).exists():
continue
read.book = item.book
read.user = user
read.save()
@ -296,7 +303,7 @@ def find_mentions(content):
def format_links(content):
''' detect and format links '''
return re.sub(
r'([^(href=")]|^)(https?:\/\/(%s([\w\.\-_\/+&\?=:;,])*))' % \
r'([^(href=")]|^|\()(https?:\/\/(%s([\w\.\-_\/+&\?=:;,])*))' % \
regex.domain,
r'\g<1><a href="\g<2>">\g<3></a>',
content)
@ -325,12 +332,13 @@ def handle_favorite(user, status):
fav_activity = favorite.to_activity()
broadcast(
user, fav_activity, privacy='direct', direct_recipients=[status.user])
create_notification(
status.user,
'FAVORITE',
related_user=user,
related_status=status
)
if status.user.local:
create_notification(
status.user,
'FAVORITE',
related_user=user,
related_status=status
)
def handle_unfavorite(user, status):
@ -348,6 +356,15 @@ def handle_unfavorite(user, status):
favorite.delete()
broadcast(user, fav_activity, direct_recipients=[status.user])
# check for notification
if status.user.local:
notification = models.Notification.objects.filter(
user=status.user, related_user=user,
related_status=status, notification_type='FAVORITE'
).first()
if notification:
notification.delete()
def handle_boost(user, status):
''' a user wishes to boost a status '''
@ -368,12 +385,13 @@ def handle_boost(user, status):
boost_activity = boost.to_activity()
broadcast(user, boost_activity)
create_notification(
status.user,
'BOOST',
related_user=user,
related_status=status
)
if status.user.local:
create_notification(
status.user,
'BOOST',
related_user=user,
related_status=status
)
def handle_unboost(user, status):
@ -386,12 +404,11 @@ def handle_unboost(user, status):
boost.delete()
broadcast(user, activity)
def handle_update_book_data(user, item):
''' broadcast the news about our book '''
broadcast(user, item.to_update_activity(user))
def handle_update_user(user):
''' broadcast editing a user's profile '''
broadcast(user, user.to_update_activity(user))
# delete related notification
if status.user.local:
notification = models.Notification.objects.filter(
user=status.user, related_user=user,
related_status=status, notification_type='BOOST'
).first()
if notification:
notification.delete()

View file

@ -74,10 +74,11 @@ input.toggle-control:checked ~ .modal.toggle-content {
}
.cover-container.is-large {
height: max-content;
max-width: 500px;
max-width: 330px;
}
.cover-container.is-large img {
max-height: 500px;
height: auto;
}
.cover-container.is-medium {
height: 150px;

View file

@ -151,6 +151,26 @@
{% endfor %}
</div>
{% endif %}
<div class="block">
<div>
<input type="radio" class="toggle-control" name="add-readthrough-form" id="hide-create-readthrough" checked>
<div class="toggle-content hidden">
<label class="button" for="add-readthrough" class="button" role="button" tabindex="0">Add read dates</label>
</div>
</div>
<div>
<input type="radio" class="toggle-control" id="add-readthrough" name="add-readthrough-form">
<div class="toggle-content hidden box">
<form name="add-readthrough" action="/create-readthrough" method="post">
{% include 'snippets/readthrough_form.html' with readthrough=None %}
<div class="field is-grouped">
<button class="button is-primary" type="submit">Create</button>
<label class="button" for="hide-create-readthrough" role="button" tabindex="0">Cancel</label>
</div>
</form>
</div>
</div>
</div>
{% if request.user.is_authenticated %}
<div class="box">

View file

@ -106,6 +106,8 @@
<label class="label" for="id_name">Name:</label>
<input type="text" name="name" maxlength="100" class="input" required="true" value="{{ shelf.name }}" id="id_name">
</div>
{% else %}
<input type="hidden" name="name" required="true" value="{{ shelf.name }}">
{% endif %}
<label class="label">

View file

@ -11,9 +11,9 @@ Follow request already sent.
{% csrf_token %}
<input type="hidden" name="user" value="{{ user.username }}">
{% if user.manually_approves_followers %}
<button class="button is-small is-primary" type="submit">Send follow request</button>
<button class="button is-small is-link" type="submit">Send follow request</button>
{% else %}
<button class="button is-small is-primary" type="submit">Follow</button>
<button class="button is-small is-link" type="submit">Follow</button>
{% endif %}
</form>
<form action="/unfollow/" method="POST" onsubmit="interact(event)" class="follow-{{ user.id }} {% if not request.user in user.followers.all %}hidden{%endif %}" data-id="follow-{{ user.id }}">

View file

@ -36,20 +36,7 @@
<div class="toggle-content hidden">
<div class="box">
<form name="edit-readthrough" action="/edit-readthrough" method="post">
{% csrf_token %}
<input type="hidden" name="id" value="{{ readthrough.id }}">
<div class="field">
<label class="label">
Started reading
<input type="date" name="start_date" class="input" id="id_start_date-{{ readthrough.id }}" value="{{ readthrough.start_date | date:"Y-m-d" }}">
</label>
</div>
<div class="field">
<label class="label">
Finished reading
<input type="date" name="finish_date" class="input" id="id_finish_date-{{ readthrough.id }}" value="{{ readthrough.finish_date | date:"Y-m-d" }}">
</label>
</div>
{% include 'snippets/readthrough_form.html' with readthrough=readthrough %}
<div class="field is-grouped">
<button class="button is-primary" type="submit">Save</button>
<label class="button" for="show-readthrough-{{ readthrough.id }}" role="button" tabindex="0">Cancel</label>

View file

@ -0,0 +1,15 @@
{% csrf_token %}
<input type="hidden" name="id" value="{{ readthrough.id }}">
<input type="hidden" name="book" value="{{ book.id }}">
<div class="field">
<label class="label">
Started reading
<input type="date" name="start_date" class="input" id="id_start_date-{{ readthrough.id }}" value="{{ readthrough.start_date | date:"Y-m-d" }}">
</label>
</div>
<div class="field">
<label class="label">
Finished reading
<input type="date" name="finish_date" class="input" id="id_finish_date-{{ readthrough.id }}" value="{{ readthrough.finish_date | date:"Y-m-d" }}">
</label>
</div>

View file

@ -82,3 +82,19 @@ class Book(TestCase):
self.assertEqual(book.edition_info, 'worm, Glorbish language, 2020')
self.assertEqual(
book.alt_text, 'Test Edition cover (worm, Glorbish language, 2020)')
def test_get_rank(self):
''' sets the data quality index for the book '''
# basic rank
self.assertEqual(self.first_edition.edition_rank, 0)
self.first_edition.description = 'hi'
self.first_edition.save()
self.assertEqual(self.first_edition.edition_rank, 1)
# default edition
self.work.default_edition = self.first_edition
self.work.save()
self.first_edition.refresh_from_db()
self.assertEqual(self.first_edition.edition_rank, 20)

View file

@ -260,6 +260,47 @@ class Outgoing(TestCase):
self.assertEqual(self.shelf.books.count(), 0)
def test_handle_reading_status_to_read(self):
''' posts shelve activities '''
shelf = self.local_user.shelf_set.get(identifier='to-read')
with patch('bookwyrm.broadcast.broadcast_task.delay'):
outgoing.handle_reading_status(
self.local_user, shelf, self.book, 'public')
status = models.GeneratedNote.objects.get()
self.assertEqual(status.user, self.local_user)
self.assertEqual(status.mention_books.first(), self.book)
self.assertEqual(status.content, 'wants to read')
def test_handle_reading_status_reading(self):
''' posts shelve activities '''
shelf = self.local_user.shelf_set.get(identifier='reading')
with patch('bookwyrm.broadcast.broadcast_task.delay'):
outgoing.handle_reading_status(
self.local_user, shelf, self.book, 'public')
status = models.GeneratedNote.objects.get()
self.assertEqual(status.user, self.local_user)
self.assertEqual(status.mention_books.first(), self.book)
self.assertEqual(status.content, 'started reading')
def test_handle_reading_status_read(self):
''' posts shelve activities '''
shelf = self.local_user.shelf_set.get(identifier='read')
with patch('bookwyrm.broadcast.broadcast_task.delay'):
outgoing.handle_reading_status(
self.local_user, shelf, self.book, 'public')
status = models.GeneratedNote.objects.get()
self.assertEqual(status.user, self.local_user)
self.assertEqual(status.mention_books.first(), self.book)
self.assertEqual(status.content, 'finished reading')
def test_handle_reading_status_other(self):
''' posts shelve activities '''
with patch('bookwyrm.broadcast.broadcast_task.delay'):
outgoing.handle_reading_status(
self.local_user, self.shelf, self.book, 'public')
self.assertFalse(models.GeneratedNote.objects.exists())
def test_handle_imported_book(self):
''' goodreads import added a book, this adds related connections '''
shelf = self.local_user.shelf_set.filter(identifier='read').first()
@ -323,6 +364,37 @@ class Outgoing(TestCase):
self.assertEqual(readthrough.finish_date.day, 25)
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/goodreads.csv')
csv_file = open(datafile, 'r')
for index, entry in enumerate(list(csv.DictReader(csv_file))):
import_item = models.ImportItem.objects.create(
job_id=import_job.id, index=index, data=entry, book=self.book)
break
with patch('bookwyrm.broadcast.broadcast_task.delay'):
outgoing.handle_imported_book(
self.local_user, import_item, False, 'public')
outgoing.handle_imported_book(
self.local_user, import_item, False, 'public')
shelf.refresh_from_db()
self.assertEqual(shelf.books.first(), self.book)
readthrough = models.ReadThrough.objects.get(user=self.local_user)
self.assertEqual(readthrough.book, self.book)
# I can't remember how to create dates and I don't want to look it up.
self.assertEqual(readthrough.start_date.year, 2020)
self.assertEqual(readthrough.start_date.month, 10)
self.assertEqual(readthrough.start_date.day, 21)
self.assertEqual(readthrough.finish_date.year, 2020)
self.assertEqual(readthrough.finish_date.month, 10)
self.assertEqual(readthrough.finish_date.day, 25)
def test_handle_imported_book_review(self):
''' goodreads review import '''
import_job = models.ImportJob.objects.create(user=self.local_user)
@ -361,6 +433,17 @@ class Outgoing(TestCase):
).exists())
def test_handle_delete_status(self):
''' marks a status as deleted '''
status = models.Status.objects.create(
user=self.local_user, content='hi')
self.assertFalse(status.deleted)
with patch('bookwyrm.broadcast.broadcast_task.delay'):
outgoing.handle_delete_status(self.local_user, status)
status.refresh_from_db()
self.assertTrue(status.deleted)
def test_handle_status(self):
''' create a status '''
form = forms.CommentForm({
@ -497,6 +580,9 @@ class Outgoing(TestCase):
self.assertEqual(
outgoing.format_links(url),
'<a href="%s">www.fish.com/</a>' % url)
self.assertEqual(
outgoing.format_links('(%s)' % url),
'(<a href="%s">www.fish.com/</a>)' % url)
url = 'https://archive.org/details/dli.granth.72113/page/n25/mode/2up'
self.assertEqual(
outgoing.format_links(url),
@ -509,3 +595,111 @@ class Outgoing(TestCase):
outgoing.format_links(url),
'<a href="%s">openlibrary.org/search' \
'?q=arkady+strugatsky&mode=everything</a>' % url)
def test_to_markdown(self):
''' this is mostly handled in other places, but nonetheless '''
text = '_hi_ and http://fish.com is <marquee>rad</marquee>'
result = outgoing.to_markdown(text)
self.assertEqual(
result,
'<p><em>hi</em> and <a href="http://fish.com">fish.com</a> ' \
'is rad</p>')
def test_handle_favorite(self):
''' create and broadcast faving a status '''
status = models.Status.objects.create(
user=self.local_user, content='hi')
with patch('bookwyrm.broadcast.broadcast_task.delay'):
outgoing.handle_favorite(self.remote_user, status)
fav = models.Favorite.objects.get()
self.assertEqual(fav.status, status)
self.assertEqual(fav.user, self.remote_user)
notification = models.Notification.objects.get()
self.assertEqual(notification.notification_type, 'FAVORITE')
self.assertEqual(notification.user, self.local_user)
self.assertEqual(notification.related_user, self.remote_user)
def test_handle_unfavorite(self):
''' unfav a status '''
status = models.Status.objects.create(
user=self.local_user, content='hi')
with patch('bookwyrm.broadcast.broadcast_task.delay'):
outgoing.handle_favorite(self.remote_user, status)
self.assertEqual(models.Favorite.objects.count(), 1)
self.assertEqual(models.Notification.objects.count(), 1)
with patch('bookwyrm.broadcast.broadcast_task.delay'):
outgoing.handle_unfavorite(self.remote_user, status)
self.assertEqual(models.Favorite.objects.count(), 0)
self.assertEqual(models.Notification.objects.count(), 0)
def test_handle_boost(self):
''' boost a status '''
status = models.Status.objects.create(
user=self.local_user, content='hi')
with patch('bookwyrm.broadcast.broadcast_task.delay'):
outgoing.handle_boost(self.remote_user, status)
boost = models.Boost.objects.get()
self.assertEqual(boost.boosted_status, status)
self.assertEqual(boost.user, self.remote_user)
self.assertEqual(boost.privacy, 'public')
notification = models.Notification.objects.get()
self.assertEqual(notification.notification_type, 'BOOST')
self.assertEqual(notification.user, self.local_user)
self.assertEqual(notification.related_user, self.remote_user)
self.assertEqual(notification.related_status, status)
def test_handle_boost_unlisted(self):
''' boost a status '''
status = models.Status.objects.create(
user=self.local_user, content='hi', privacy='unlisted')
with patch('bookwyrm.broadcast.broadcast_task.delay'):
outgoing.handle_boost(self.remote_user, status)
boost = models.Boost.objects.get()
self.assertEqual(boost.privacy, 'unlisted')
def test_handle_boost_private(self):
''' boost a status '''
status = models.Status.objects.create(
user=self.local_user, content='hi', privacy='followers')
with patch('bookwyrm.broadcast.broadcast_task.delay'):
outgoing.handle_boost(self.remote_user, status)
self.assertFalse(models.Boost.objects.exists())
def test_handle_boost_twice(self):
''' boost a status '''
status = models.Status.objects.create(
user=self.local_user, content='hi')
with patch('bookwyrm.broadcast.broadcast_task.delay'):
outgoing.handle_boost(self.remote_user, status)
outgoing.handle_boost(self.remote_user, status)
self.assertEqual(models.Boost.objects.count(), 1)
def test_handle_unboost(self):
''' undo a boost '''
status = models.Status.objects.create(
user=self.local_user, content='hi')
with patch('bookwyrm.broadcast.broadcast_task.delay'):
outgoing.handle_boost(self.remote_user, status)
self.assertEqual(models.Boost.objects.count(), 1)
self.assertEqual(models.Notification.objects.count(), 1)
with patch('bookwyrm.broadcast.broadcast_task.delay'):
outgoing.handle_unboost(self.remote_user, status)
self.assertEqual(models.Boost.objects.count(), 0)
self.assertEqual(models.Notification.objects.count(), 0)

View file

@ -1,12 +1,14 @@
''' test for app action functionality '''
from unittest.mock import patch
import dateutil
from django.core.exceptions import PermissionDenied
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.http.response import Http404
from django.test import TestCase
from django.test.client import RequestFactory
from django.utils import timezone
from bookwyrm import forms, models, view_actions as actions
from bookwyrm.settings import DOMAIN
@ -358,6 +360,126 @@ class ViewActions(TestCase):
self.assertEqual(resp.template_name, 'edit_author.html')
def test_edit_shelf_privacy(self):
''' set name or privacy on shelf '''
shelf = self.local_user.shelf_set.get(identifier='to-read')
self.assertEqual(shelf.privacy, 'public')
request = self.factory.post(
'', {
'privacy': 'unlisted',
'user': self.local_user.id,
'name': 'To Read',
})
request.user = self.local_user
actions.edit_shelf(request, shelf.id)
shelf.refresh_from_db()
self.assertEqual(shelf.privacy, 'unlisted')
def test_edit_shelf_name(self):
''' change the name of an editable shelf '''
shelf = models.Shelf.objects.create(
name='Test Shelf', user=self.local_user)
self.assertEqual(shelf.privacy, 'public')
request = self.factory.post(
'', {
'privacy': 'public',
'user': self.local_user.id,
'name': 'cool name'
})
request.user = self.local_user
actions.edit_shelf(request, shelf.id)
shelf.refresh_from_db()
self.assertEqual(shelf.name, 'cool name')
self.assertEqual(shelf.identifier, 'testshelf-%d' % shelf.id)
def test_edit_shelf_name_not_editable(self):
''' can't change the name of an non-editable shelf '''
shelf = self.local_user.shelf_set.get(identifier='to-read')
self.assertEqual(shelf.privacy, 'public')
request = self.factory.post(
'', {
'privacy': 'public',
'user': self.local_user.id,
'name': 'cool name'
})
request.user = self.local_user
actions.edit_shelf(request, shelf.id)
self.assertEqual(shelf.name, 'To Read')
def test_edit_readthrough(self):
''' adding dates to an ongoing readthrough '''
start = timezone.make_aware(dateutil.parser.parse('2021-01-03'))
readthrough = models.ReadThrough.objects.create(
book=self.book, user=self.local_user, start_date=start)
request = self.factory.post(
'', {
'start_date': '2017-01-01',
'finish_date': '2018-03-07',
'book': '',
'id': readthrough.id,
})
request.user = self.local_user
actions.edit_readthrough(request)
readthrough.refresh_from_db()
self.assertEqual(readthrough.start_date.year, 2017)
self.assertEqual(readthrough.start_date.month, 1)
self.assertEqual(readthrough.start_date.day, 1)
self.assertEqual(readthrough.finish_date.year, 2018)
self.assertEqual(readthrough.finish_date.month, 3)
self.assertEqual(readthrough.finish_date.day, 7)
self.assertEqual(readthrough.book, self.book)
def test_delete_readthrough(self):
''' remove a readthrough '''
readthrough = models.ReadThrough.objects.create(
book=self.book, user=self.local_user)
models.ReadThrough.objects.create(
book=self.book, user=self.local_user)
request = self.factory.post(
'', {
'id': readthrough.id,
})
request.user = self.local_user
actions.delete_readthrough(request)
self.assertFalse(
models.ReadThrough.objects.filter(id=readthrough.id).exists())
def test_create_readthrough(self):
''' adding new read dates '''
request = self.factory.post(
'', {
'start_date': '2017-01-01',
'finish_date': '2018-03-07',
'book': self.book.id,
'id': '',
})
request.user = self.local_user
actions.create_readthrough(request)
readthrough = models.ReadThrough.objects.get()
self.assertEqual(readthrough.start_date.year, 2017)
self.assertEqual(readthrough.start_date.month, 1)
self.assertEqual(readthrough.start_date.day, 1)
self.assertEqual(readthrough.finish_date.year, 2018)
self.assertEqual(readthrough.finish_date.month, 3)
self.assertEqual(readthrough.finish_date.day, 7)
self.assertEqual(readthrough.book, self.book)
self.assertEqual(readthrough.user, self.local_user)
def test_tag(self):
''' add a tag to a book '''
request = self.factory.post(

View file

@ -570,6 +570,16 @@ class Views(TestCase):
self.assertEqual(result.status_code, 200)
request = self.factory.get('/?page=1')
request.user = self.local_user
with patch('bookwyrm.views.is_api_request') as is_api:
is_api.return_value = True
result = views.shelf_page(
request, self.local_user.username, shelf.identifier)
self.assertIsInstance(result, ActivitypubResponse)
self.assertEqual(result.status_code, 200)
def test_is_bookwyrm_request(self):
''' checks if a request came from a bookwyrm instance '''
request = self.factory.get('', {'q': 'Test Book'})

View file

@ -110,6 +110,7 @@ urlpatterns = [
re_path(r'^switch-edition/?$', actions.switch_edition),
re_path(r'^edit-readthrough/?$', actions.edit_readthrough),
re_path(r'^delete-readthrough/?$', actions.delete_readthrough),
re_path(r'^create-readthrough/?$', actions.create_readthrough),
re_path(r'^rate/?$', actions.rate),
re_path(r'^review/?$', actions.review),

View file

@ -210,7 +210,7 @@ def edit_profile(request):
user.avatar.save(filename, ContentFile(output.getvalue()))
user.save()
outgoing.handle_update_user(user)
broadcast(user, user.to_update_activity(user))
return redirect('/user/%s' % request.user.localname)
@ -241,7 +241,7 @@ def edit_book(request, book_id):
return TemplateResponse(request, 'edit_book.html', data)
book = form.save()
outgoing.handle_update_book_data(request.user, book)
broadcast(request.user, book.to_update_activity(request.user))
return redirect('/book/%s' % book.id)
@ -288,7 +288,7 @@ def upload_cover(request, book_id):
book.cover = form.files['cover']
book.save()
outgoing.handle_update_book_data(request.user, book)
broadcast(request.user, book.to_update_activity(request.user))
return redirect('/book/%s' % book.id)
@ -307,7 +307,7 @@ def add_description(request, book_id):
book.description = description
book.save()
outgoing.handle_update_book_data(request.user, book)
broadcast(request.user, book.to_update_activity(request.user))
return redirect('/book/%s' % book.id)
@ -328,7 +328,7 @@ def edit_author(request, author_id):
return TemplateResponse(request, 'edit_author.html', data)
author = form.save()
outgoing.handle_update_book_data(request.user, author)
broadcast(request.user, author.to_update_activity(request.user))
return redirect('/author/%s' % author.id)
@ -352,13 +352,14 @@ def edit_shelf(request, shelf_id):
shelf = get_object_or_404(models.Shelf, id=shelf_id)
if request.user != shelf.user:
return HttpResponseBadRequest()
if not shelf.editable and request.POST.get('name') != shelf.name:
return HttpResponseBadRequest()
form = forms.ShelfForm(request.POST, instance=shelf)
if not form.is_valid():
return redirect(request.headers.get('Referer', '/'))
return redirect(shelf.local_path)
shelf = form.save()
return redirect('/user/%s/shelf/%s' % \
(request.user.localname, shelf.identifier))
return redirect(shelf.local_path)
@login_required
@ -402,7 +403,7 @@ def shelve(request):
request.user,
desired_shelf,
book,
privacy='public'
privacy=desired_shelf.privacy
)
return redirect('/')
@ -522,6 +523,18 @@ def delete_readthrough(request):
return redirect(request.headers.get('Referer', '/'))
@login_required
@require_POST
def create_readthrough(request):
''' can't use the form because the dates are too finnicky '''
book = get_object_or_404(models.Edition, id=request.POST.get('book'))
readthrough = update_readthrough(request, create=True, book=book)
if not readthrough:
return redirect(book.local_path)
readthrough.save()
return redirect(request.headers.get('Referer', '/'))
@login_required
@require_POST
def rate(request):
@ -824,7 +837,7 @@ def update_readthrough(request, book=None, create=True):
start_date = request.POST.get('start_date')
if start_date:
try:
start_date = dateutil.parser.parse(start_date)
start_date = timezone.make_aware(dateutil.parser.parse(start_date))
readthrough.start_date = start_date
except ParserError:
pass
@ -832,9 +845,13 @@ def update_readthrough(request, book=None, create=True):
finish_date = request.POST.get('finish_date')
if finish_date:
try:
finish_date = dateutil.parser.parse(finish_date)
finish_date = timezone.make_aware(
dateutil.parser.parse(finish_date))
readthrough.finish_date = finish_date
except ParserError:
pass
if not readthrough.start_date and not readthrough.finish_date:
return None
return readthrough

View file

@ -736,7 +736,7 @@ def editions_page(request, book_id):
data = {
'title': 'Editions of %s' % work.title,
'editions': work.editions.all(),
'editions': work.editions.order_by('-edition_rank').all(),
'work': work,
}
return TemplateResponse(request, 'editions.html', data)