From 2b27889457bfb213742db7e9bf8c516bc634d0e7 Mon Sep 17 00:00:00 2001 From: Thomas Versteeg Date: Fri, 11 Feb 2022 14:33:46 +0100 Subject: [PATCH 01/85] Add 'Partially Read' shelf --- .../migrations/0134_alter_partially_read.py | 29 +++++++++++++ bookwyrm/models/shelf.py | 3 +- bookwyrm/models/status.py | 2 +- bookwyrm/models/user.py | 4 ++ .../templates/get_started/book_preview.html | 1 + bookwyrm/templates/shelf/shelf.html | 1 + .../reading_modals/partially_read_modal.html | 42 +++++++++++++++++++ .../templates/snippets/shelf_selector.html | 10 +++++ .../snippets/shelve_button/shelve_button.html | 3 ++ .../shelve_button_dropdown_options.html | 7 ++++ .../shelve_button/shelve_button_options.html | 7 ++++ .../snippets/translated_shelf_name.html | 2 + bookwyrm/templates/user/user.html | 3 +- bookwyrm/tests/models/test_user_model.py | 2 + bookwyrm/urls.py | 2 +- bookwyrm/views/reading.py | 2 + 16 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 bookwyrm/migrations/0134_alter_partially_read.py create mode 100644 bookwyrm/templates/snippets/reading_modals/partially_read_modal.html diff --git a/bookwyrm/migrations/0134_alter_partially_read.py b/bookwyrm/migrations/0134_alter_partially_read.py new file mode 100644 index 000000000..85e9ed9f8 --- /dev/null +++ b/bookwyrm/migrations/0134_alter_partially_read.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.11 on 2022-02-11 13:19 + +import bookwyrm.models.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0133_alter_listitem_notes'), + ] + + operations = [ + migrations.AlterField( + model_name='comment', + name='reading_status', + field=bookwyrm.models.fields.CharField(blank=True, choices=[('to-read', 'To-Read'), ('reading', 'Reading'), ('read', 'Read'), ('partially-read', 'Partially-Read')], max_length=255, null=True), + ), + migrations.AlterField( + model_name='quotation', + name='reading_status', + field=bookwyrm.models.fields.CharField(blank=True, choices=[('to-read', 'To-Read'), ('reading', 'Reading'), ('read', 'Read'), ('partially-read', 'Partially-Read')], max_length=255, null=True), + ), + migrations.AlterField( + model_name='review', + name='reading_status', + field=bookwyrm.models.fields.CharField(blank=True, choices=[('to-read', 'To-Read'), ('reading', 'Reading'), ('read', 'Read'), ('partially-read', 'Partially-Read')], max_length=255, null=True), + ), + ] diff --git a/bookwyrm/models/shelf.py b/bookwyrm/models/shelf.py index 320d495d2..4c57eb96c 100644 --- a/bookwyrm/models/shelf.py +++ b/bookwyrm/models/shelf.py @@ -17,8 +17,9 @@ class Shelf(OrderedCollectionMixin, BookWyrmModel): TO_READ = "to-read" READING = "reading" READ_FINISHED = "read" + PARTIALLY_READ = "partially-read" - READ_STATUS_IDENTIFIERS = (TO_READ, READING, READ_FINISHED) + READ_STATUS_IDENTIFIERS = (TO_READ, READING, READ_FINISHED, PARTIALLY_READ) name = fields.CharField(max_length=100) identifier = models.CharField(max_length=100) diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 29b3ba9cc..99756df51 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -265,7 +265,7 @@ class GeneratedNote(Status): ReadingStatusChoices = models.TextChoices( - "ReadingStatusChoices", ["to-read", "reading", "read"] + "ReadingStatusChoices", ["to-read", "reading", "read", "partially-read"] ) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 6367dcaef..223bad0fa 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -372,6 +372,10 @@ class User(OrderedCollectionPageMixin, AbstractUser): "name": "Read", "identifier": "read", }, + { + "name": "Partially Read", + "identifier": "partially-read", + }, ] for shelf in shelves: diff --git a/bookwyrm/templates/get_started/book_preview.html b/bookwyrm/templates/get_started/book_preview.html index 8a20d0d77..51a5a4e6e 100644 --- a/bookwyrm/templates/get_started/book_preview.html +++ b/bookwyrm/templates/get_started/book_preview.html @@ -10,6 +10,7 @@ {% if shelf.identifier == 'to-read' %}{% trans "To Read" %} {% elif shelf.identifier == 'reading' %}{% trans "Currently Reading" %} {% elif shelf.identifier == 'read' %}{% trans "Read" %} + {% elif shelf.identifier == 'partially-read' %}{% trans "Partially Read" %} {% else %}{{ shelf.name }}{% endif %} {% endfor %} diff --git a/bookwyrm/templates/shelf/shelf.html b/bookwyrm/templates/shelf/shelf.html index cc4bb1436..67faf0a48 100644 --- a/bookwyrm/templates/shelf/shelf.html +++ b/bookwyrm/templates/shelf/shelf.html @@ -86,6 +86,7 @@ {% if shelf.identifier == 'to-read' %}{% trans "To Read" %} {% elif shelf.identifier == 'reading' %}{% trans "Currently Reading" %} {% elif shelf.identifier == 'read' %}{% trans "Read" %} + {% elif shelf.identifier == 'partially-read' %}{% trans "Partially Read" %} {% else %}{{ shelf.name }}{% endif %} {% include 'snippets/privacy-icons.html' with item=shelf %} diff --git a/bookwyrm/templates/snippets/reading_modals/partially_read_modal.html b/bookwyrm/templates/snippets/reading_modals/partially_read_modal.html new file mode 100644 index 000000000..60c39c408 --- /dev/null +++ b/bookwyrm/templates/snippets/reading_modals/partially_read_modal.html @@ -0,0 +1,42 @@ +{% extends 'snippets/reading_modals/layout.html' %} +{% load i18n %} +{% load utilities %} + +{% block modal-title %} +{% blocktrans trimmed with book_title=book|book_title %} +Partially Read "{{ book_title }}" +{% endblocktrans %} +{% endblock %} + +{% block modal-form-open %} +
+{% csrf_token %} + + + +{% endblock %} + +{% block reading-dates %} +
+
+
+ + +
+
+
+
+ + +
+
+
+{% endblock %} + +{% block form %} +{% include "snippets/reading_modals/form.html" with optional=True type="partially_read_model" %} +{% endblock %} diff --git a/bookwyrm/templates/snippets/shelf_selector.html b/bookwyrm/templates/snippets/shelf_selector.html index 197cf5b6c..7589ffc61 100644 --- a/bookwyrm/templates/snippets/shelf_selector.html +++ b/bookwyrm/templates/snippets/shelf_selector.html @@ -49,6 +49,13 @@ {% join "finish_reading" uuid as modal_id %} {% include 'snippets/shelve_button/modal_button.html' with class=button_class fallback_url=fallback_url %} +{% elif shelf.identifier == 'partially-read' %} + +{% trans "Partially read" as button_text %} +{% url 'reading-status' 'stop' book.id as fallback_url %} +{% join "partially_read" uuid as modal_id %} +{% include 'snippets/shelve_button/modal_button.html' with class=button_class fallback_url=fallback_url %} + {% elif shelf.identifier == 'to-read' %} {% trans "Want to read" as button_text %} @@ -97,5 +104,8 @@ {% join "finish_reading" uuid as modal_id %} {% include 'snippets/reading_modals/finish_reading_modal.html' with book=active_shelf.book id=modal_id move_from=current.id readthrough=readthrough refresh=True class="" %} +{% join "partially_read" uuid as modal_id %} +{% include 'snippets/reading_modals/partially_read_modal.html' with book=active_shelf.book id=modal_id move_from=current.id readthrough=readthrough refresh=True class="" %} + {% endwith %} {% endblock %} diff --git a/bookwyrm/templates/snippets/shelve_button/shelve_button.html b/bookwyrm/templates/snippets/shelve_button/shelve_button.html index 04dc4e4b0..6944db6af 100644 --- a/bookwyrm/templates/snippets/shelve_button/shelve_button.html +++ b/bookwyrm/templates/snippets/shelve_button/shelve_button.html @@ -29,6 +29,9 @@ {% join "finish_reading" uuid as modal_id %} {% include 'snippets/reading_modals/finish_reading_modal.html' with book=active_shelf_book id=modal_id readthrough=readthrough class="" %} +{% join "partially_read" uuid as modal_id %} +{% include 'snippets/reading_modals/partially_read_modal.html' with book=active_shelf_book id=modal_id readthrough=readthrough class="" %} + {% join "progress_update" uuid as modal_id %} {% include 'snippets/reading_modals/progress_update_modal.html' with book=active_shelf_book id=modal_id readthrough=readthrough class="" %} diff --git a/bookwyrm/templates/snippets/shelve_button/shelve_button_dropdown_options.html b/bookwyrm/templates/snippets/shelve_button/shelve_button_dropdown_options.html index 1fa26a886..23e5f4a44 100644 --- a/bookwyrm/templates/snippets/shelve_button/shelve_button_dropdown_options.html +++ b/bookwyrm/templates/snippets/shelve_button/shelve_button_dropdown_options.html @@ -26,6 +26,13 @@ {% join "finish_reading" button_uuid as modal_id %} {% include 'snippets/shelve_button/modal_button.html' with class=class fallback_url=fallback_url %} + {% elif shelf.identifier == 'partially-read' %} + + {% trans "Partially read" as button_text %} + {% url 'reading-status' 'stop' book.id as fallback_url %} + {% join "partially_read" button_uuid as modal_id %} + {% include 'snippets/shelve_button/modal_button.html' with class=class fallback_url=fallback_url %} + {% elif shelf.identifier == 'to-read' %} {% trans "Want to read" as button_text %} diff --git a/bookwyrm/templates/snippets/shelve_button/shelve_button_options.html b/bookwyrm/templates/snippets/shelve_button/shelve_button_options.html index 04f4bdc26..0a711addd 100644 --- a/bookwyrm/templates/snippets/shelve_button/shelve_button_options.html +++ b/bookwyrm/templates/snippets/shelve_button/shelve_button_options.html @@ -33,6 +33,13 @@ {% join "finish_reading" button_uuid as modal_id %} {% include 'snippets/shelve_button/modal_button.html' with class=class fallback_url=fallback_url %} + {% elif shelf.identifier == 'partially-read' %} + + {% trans "Stop reading" as button_text %} + {% url 'reading-status' 'stop' book.id as fallback_url %} + {% join "partially_read" button_uuid as modal_id %} + {% include 'snippets/shelve_button/modal_button.html' with class=class fallback_url=fallback_url %} + {% elif shelf.identifier == 'to-read' %} {% trans "Want to read" as button_text %} diff --git a/bookwyrm/templates/snippets/translated_shelf_name.html b/bookwyrm/templates/snippets/translated_shelf_name.html index 4da47e370..dc1c579b3 100644 --- a/bookwyrm/templates/snippets/translated_shelf_name.html +++ b/bookwyrm/templates/snippets/translated_shelf_name.html @@ -7,6 +7,8 @@ {% trans "Currently Reading" %} {% elif shelf.identifier == 'read' %} {% trans "Read" %} +{% elif shelf.identifier == 'partially-read' %} + {% trans "Partially Read" %} {% else %} {{ shelf.name }} {% endif %} diff --git a/bookwyrm/templates/user/user.html b/bookwyrm/templates/user/user.html index ccc4a44ea..8d7fd31a2 100755 --- a/bookwyrm/templates/user/user.html +++ b/bookwyrm/templates/user/user.html @@ -33,8 +33,9 @@ {% if shelf.name == 'To Read' %}{% trans "To Read" %} {% elif shelf.name == 'Currently Reading' %}{% trans "Currently Reading" %} {% elif shelf.name == 'Read' %}{% trans "Read" %} + {% elif shelf.name == 'Partially Read' %}{% trans "Partially Read" %} {% else %}{{ shelf.name }}{% endif %} - {% if shelf.size > 3 %}({% blocktrans with size=shelf.size %}View all {{ size }}{% endblocktrans %}){% endif %} + {% if shelf.size > 4 %}({% blocktrans with size=shelf.size %}View all {{ size }}{% endblocktrans %}){% endif %}
{% for book in shelf.books %} diff --git a/bookwyrm/tests/models/test_user_model.py b/bookwyrm/tests/models/test_user_model.py index aa62dce3a..8d3439632 100644 --- a/bookwyrm/tests/models/test_user_model.py +++ b/bookwyrm/tests/models/test_user_model.py @@ -58,10 +58,12 @@ class User(TestCase): self.assertTrue("To Read" in names) self.assertTrue("Currently Reading" in names) self.assertTrue("Read" in names) + self.assertTrue("Partially Read" in names) ids = [s.identifier for s in shelves] self.assertTrue("to-read" in ids) self.assertTrue("reading" in ids) self.assertTrue("read" in ids) + self.assertTrue("partially-read" in ids) def test_activitypub_serialize(self): activity = self.user.to_activity() diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 79d868c95..670610bd7 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -543,7 +543,7 @@ urlpatterns = [ name="reading-status-update", ), re_path( - r"^reading-status/(?Pwant|start|finish)/(?P\d+)/?$", + r"^reading-status/(?Pwant|start|finish|stop)/(?P\d+)/?$", views.ReadingStatus.as_view(), name="reading-status", ), diff --git a/bookwyrm/views/reading.py b/bookwyrm/views/reading.py index 2cd05202c..b80bc50df 100644 --- a/bookwyrm/views/reading.py +++ b/bookwyrm/views/reading.py @@ -29,6 +29,7 @@ class ReadingStatus(View): "want": "want.html", "start": "start.html", "finish": "finish.html", + "stop": "stop.html", }.get(status) if not template: return HttpResponseNotFound() @@ -41,6 +42,7 @@ class ReadingStatus(View): "want": models.Shelf.TO_READ, "start": models.Shelf.READING, "finish": models.Shelf.READ_FINISHED, + "stop": models.Shelf.PARTIALLY_READ, }.get(status) if not identifier: return HttpResponseBadRequest() From bc89dd704127e5c289c3576e87f6e8d86b75591b Mon Sep 17 00:00:00 2001 From: Thomas Versteeg Date: Sat, 12 Feb 2022 11:19:00 +0100 Subject: [PATCH 02/85] Change shelf Finished label When the shelf is read the label is 'Finished', otherwise it's 'Until'. --- bookwyrm/templates/shelf/shelf.html | 4 ++-- .../snippets/reading_modals/partially_read_modal.html | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bookwyrm/templates/shelf/shelf.html b/bookwyrm/templates/shelf/shelf.html index 67faf0a48..2a980e15a 100644 --- a/bookwyrm/templates/shelf/shelf.html +++ b/bookwyrm/templates/shelf/shelf.html @@ -151,7 +151,7 @@ {% if is_self %} {% trans "Shelved" as text %}{% include 'snippets/table-sort-header.html' with field="shelved_date" sort=sort text=text %} {% trans "Started" as text %}{% include 'snippets/table-sort-header.html' with field="start_date" sort=sort text=text %} - {% trans "Finished" as text %}{% include 'snippets/table-sort-header.html' with field="finish_date" sort=sort text=text %} + {% if shelf.identifier == 'read' %}{% trans "Finished" as text %}{% else %}{% trans "Until" as text %}{% endif %}{% include 'snippets/table-sort-header.html' with field="finish_date" sort=sort text=text %} {% endif %} {% trans "Rating" as text %}{% include 'snippets/table-sort-header.html' with field="rating" sort=sort text=text %} {% endif %} @@ -181,7 +181,7 @@ {{ book.start_date|naturalday|default_if_none:""}} - + {{ book.finish_date|naturalday|default_if_none:""}} {% endif %} diff --git a/bookwyrm/templates/snippets/reading_modals/partially_read_modal.html b/bookwyrm/templates/snippets/reading_modals/partially_read_modal.html index 60c39c408..2260f8c16 100644 --- a/bookwyrm/templates/snippets/reading_modals/partially_read_modal.html +++ b/bookwyrm/templates/snippets/reading_modals/partially_read_modal.html @@ -28,10 +28,10 @@ Partially Read "{{ book_title }}"
-
From c88b34814f2530b5d14197c89743a00a31cebfb0 Mon Sep 17 00:00:00 2001 From: Thomas Versteeg Date: Sat, 12 Feb 2022 19:44:06 +0100 Subject: [PATCH 03/85] Rename 'Partially Read' to 'Stopped Reading' --- .../migrations/0134_alter_partially_read.py | 29 --------- .../migrations/0134_alter_stopped_reading.py | 59 +++++++++++++++++++ bookwyrm/models/shelf.py | 4 +- bookwyrm/models/status.py | 2 +- bookwyrm/models/user.py | 4 +- .../templates/get_started/book_preview.html | 2 +- bookwyrm/templates/shelf/shelf.html | 6 +- ...ead_modal.html => stop_reading_modal.html} | 6 +- .../templates/snippets/shelf_selector.html | 10 ++-- .../snippets/shelve_button/shelve_button.html | 4 +- .../shelve_button_dropdown_options.html | 6 +- .../shelve_button/shelve_button_options.html | 4 +- .../snippets/translated_shelf_name.html | 4 +- bookwyrm/templates/user/user.html | 2 +- bookwyrm/templatetags/shelf_tags.py | 29 +++++---- bookwyrm/tests/models/test_user_model.py | 4 +- bookwyrm/views/reading.py | 2 +- 17 files changed, 105 insertions(+), 72 deletions(-) delete mode 100644 bookwyrm/migrations/0134_alter_partially_read.py create mode 100644 bookwyrm/migrations/0134_alter_stopped_reading.py rename bookwyrm/templates/snippets/reading_modals/{partially_read_modal.html => stop_reading_modal.html} (85%) diff --git a/bookwyrm/migrations/0134_alter_partially_read.py b/bookwyrm/migrations/0134_alter_partially_read.py deleted file mode 100644 index 85e9ed9f8..000000000 --- a/bookwyrm/migrations/0134_alter_partially_read.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 3.2.11 on 2022-02-11 13:19 - -import bookwyrm.models.fields -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('bookwyrm', '0133_alter_listitem_notes'), - ] - - operations = [ - migrations.AlterField( - model_name='comment', - name='reading_status', - field=bookwyrm.models.fields.CharField(blank=True, choices=[('to-read', 'To-Read'), ('reading', 'Reading'), ('read', 'Read'), ('partially-read', 'Partially-Read')], max_length=255, null=True), - ), - migrations.AlterField( - model_name='quotation', - name='reading_status', - field=bookwyrm.models.fields.CharField(blank=True, choices=[('to-read', 'To-Read'), ('reading', 'Reading'), ('read', 'Read'), ('partially-read', 'Partially-Read')], max_length=255, null=True), - ), - migrations.AlterField( - model_name='review', - name='reading_status', - field=bookwyrm.models.fields.CharField(blank=True, choices=[('to-read', 'To-Read'), ('reading', 'Reading'), ('read', 'Read'), ('partially-read', 'Partially-Read')], max_length=255, null=True), - ), - ] diff --git a/bookwyrm/migrations/0134_alter_stopped_reading.py b/bookwyrm/migrations/0134_alter_stopped_reading.py new file mode 100644 index 000000000..c0c2c6520 --- /dev/null +++ b/bookwyrm/migrations/0134_alter_stopped_reading.py @@ -0,0 +1,59 @@ +# Generated by Django 3.2.11 on 2022-02-11 13:19 + +import bookwyrm.models.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0133_alter_listitem_notes"), + ] + + operations = [ + migrations.AlterField( + model_name="comment", + name="reading_status", + field=bookwyrm.models.fields.CharField( + blank=True, + choices=[ + ("to-read", "To-Read"), + ("reading", "Reading"), + ("read", "Read"), + ("stopped-reading", "Stopped-Reading"), + ], + max_length=255, + null=True, + ), + ), + migrations.AlterField( + model_name="quotation", + name="reading_status", + field=bookwyrm.models.fields.CharField( + blank=True, + choices=[ + ("to-read", "To-Read"), + ("reading", "Reading"), + ("read", "Read"), + ("stopped-reading", "Stopped-Reading"), + ], + max_length=255, + null=True, + ), + ), + migrations.AlterField( + model_name="review", + name="reading_status", + field=bookwyrm.models.fields.CharField( + blank=True, + choices=[ + ("to-read", "To-Read"), + ("reading", "Reading"), + ("read", "Read"), + ("stopped-reading", "Stopped-Reading"), + ], + max_length=255, + null=True, + ), + ), + ] diff --git a/bookwyrm/models/shelf.py b/bookwyrm/models/shelf.py index 4c57eb96c..14bb45c39 100644 --- a/bookwyrm/models/shelf.py +++ b/bookwyrm/models/shelf.py @@ -17,9 +17,9 @@ class Shelf(OrderedCollectionMixin, BookWyrmModel): TO_READ = "to-read" READING = "reading" READ_FINISHED = "read" - PARTIALLY_READ = "partially-read" + STOPPED_READING = "stopped-reading" - READ_STATUS_IDENTIFIERS = (TO_READ, READING, READ_FINISHED, PARTIALLY_READ) + READ_STATUS_IDENTIFIERS = (TO_READ, READING, READ_FINISHED, STOPPED_READING) name = fields.CharField(max_length=100) identifier = models.CharField(max_length=100) diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 99756df51..66fd76aa1 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -265,7 +265,7 @@ class GeneratedNote(Status): ReadingStatusChoices = models.TextChoices( - "ReadingStatusChoices", ["to-read", "reading", "read", "partially-read"] + "ReadingStatusChoices", ["to-read", "reading", "read", "stopped-reading"] ) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 223bad0fa..157b5f88e 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -373,8 +373,8 @@ class User(OrderedCollectionPageMixin, AbstractUser): "identifier": "read", }, { - "name": "Partially Read", - "identifier": "partially-read", + "name": "Stopped Reading", + "identifier": "stopped-reading", }, ] diff --git a/bookwyrm/templates/get_started/book_preview.html b/bookwyrm/templates/get_started/book_preview.html index 51a5a4e6e..9cfb56b00 100644 --- a/bookwyrm/templates/get_started/book_preview.html +++ b/bookwyrm/templates/get_started/book_preview.html @@ -10,7 +10,7 @@ {% if shelf.identifier == 'to-read' %}{% trans "To Read" %} {% elif shelf.identifier == 'reading' %}{% trans "Currently Reading" %} {% elif shelf.identifier == 'read' %}{% trans "Read" %} - {% elif shelf.identifier == 'partially-read' %}{% trans "Partially Read" %} + {% elif shelf.identifier == 'stopped-reading' %}{% trans "Stopped Reading" %} {% else %}{{ shelf.name }}{% endif %} {% endfor %} diff --git a/bookwyrm/templates/shelf/shelf.html b/bookwyrm/templates/shelf/shelf.html index 2a980e15a..b36dc01cb 100644 --- a/bookwyrm/templates/shelf/shelf.html +++ b/bookwyrm/templates/shelf/shelf.html @@ -86,7 +86,7 @@ {% if shelf.identifier == 'to-read' %}{% trans "To Read" %} {% elif shelf.identifier == 'reading' %}{% trans "Currently Reading" %} {% elif shelf.identifier == 'read' %}{% trans "Read" %} - {% elif shelf.identifier == 'partially-read' %}{% trans "Partially Read" %} + {% elif shelf.identifier == 'stopped-reading' %}{% trans "Stopped Reading" %} {% else %}{{ shelf.name }}{% endif %} {% include 'snippets/privacy-icons.html' with item=shelf %} @@ -151,7 +151,7 @@ {% if is_self %} {% trans "Shelved" as text %}{% include 'snippets/table-sort-header.html' with field="shelved_date" sort=sort text=text %} {% trans "Started" as text %}{% include 'snippets/table-sort-header.html' with field="start_date" sort=sort text=text %} - {% if shelf.identifier == 'read' %}{% trans "Finished" as text %}{% else %}{% trans "Until" as text %}{% endif %}{% include 'snippets/table-sort-header.html' with field="finish_date" sort=sort text=text %} + {% if shelf.identifier == 'read' %}{% trans "Finished" as text %}{% else %}{% trans "Until" as text %}{% endif %}{% include 'snippets/table-sort-header.html' with field="finish_date" sort=sort text=text %} {% endif %} {% trans "Rating" as text %}{% include 'snippets/table-sort-header.html' with field="rating" sort=sort text=text %} {% endif %} @@ -181,7 +181,7 @@ {{ book.start_date|naturalday|default_if_none:""}} - + {{ book.finish_date|naturalday|default_if_none:""}} {% endif %} diff --git a/bookwyrm/templates/snippets/reading_modals/partially_read_modal.html b/bookwyrm/templates/snippets/reading_modals/stop_reading_modal.html similarity index 85% rename from bookwyrm/templates/snippets/reading_modals/partially_read_modal.html rename to bookwyrm/templates/snippets/reading_modals/stop_reading_modal.html index 2260f8c16..163a34155 100644 --- a/bookwyrm/templates/snippets/reading_modals/partially_read_modal.html +++ b/bookwyrm/templates/snippets/reading_modals/stop_reading_modal.html @@ -4,12 +4,12 @@ {% block modal-title %} {% blocktrans trimmed with book_title=book|book_title %} -Partially Read "{{ book_title }}" +Stop Reading "{{ book_title }}" {% endblocktrans %} {% endblock %} {% block modal-form-open %} - + {% csrf_token %} @@ -38,5 +38,5 @@ Partially Read "{{ book_title }}" {% endblock %} {% block form %} -{% include "snippets/reading_modals/form.html" with optional=True type="partially_read_model" %} +{% include "snippets/reading_modals/form.html" with optional=True type="stop_modal" %} {% endblock %} diff --git a/bookwyrm/templates/snippets/shelf_selector.html b/bookwyrm/templates/snippets/shelf_selector.html index 7589ffc61..ef245cec8 100644 --- a/bookwyrm/templates/snippets/shelf_selector.html +++ b/bookwyrm/templates/snippets/shelf_selector.html @@ -49,11 +49,11 @@ {% join "finish_reading" uuid as modal_id %} {% include 'snippets/shelve_button/modal_button.html' with class=button_class fallback_url=fallback_url %} -{% elif shelf.identifier == 'partially-read' %} +{% elif shelf.identifier == 'stopped-reading' %} -{% trans "Partially read" as button_text %} +{% trans "Stopped reading" as button_text %} {% url 'reading-status' 'stop' book.id as fallback_url %} -{% join "partially_read" uuid as modal_id %} +{% join "stop_reading" uuid as modal_id %} {% include 'snippets/shelve_button/modal_button.html' with class=button_class fallback_url=fallback_url %} {% elif shelf.identifier == 'to-read' %} @@ -104,8 +104,8 @@ {% join "finish_reading" uuid as modal_id %} {% include 'snippets/reading_modals/finish_reading_modal.html' with book=active_shelf.book id=modal_id move_from=current.id readthrough=readthrough refresh=True class="" %} -{% join "partially_read" uuid as modal_id %} -{% include 'snippets/reading_modals/partially_read_modal.html' with book=active_shelf.book id=modal_id move_from=current.id readthrough=readthrough refresh=True class="" %} +{% join "stop_reading" uuid as modal_id %} +{% include 'snippets/reading_modals/stop_reading_modal.html' with book=active_shelf.book id=modal_id move_from=current.id readthrough=readthrough refresh=True class="" %} {% endwith %} {% endblock %} diff --git a/bookwyrm/templates/snippets/shelve_button/shelve_button.html b/bookwyrm/templates/snippets/shelve_button/shelve_button.html index 6944db6af..010bff055 100644 --- a/bookwyrm/templates/snippets/shelve_button/shelve_button.html +++ b/bookwyrm/templates/snippets/shelve_button/shelve_button.html @@ -29,8 +29,8 @@ {% join "finish_reading" uuid as modal_id %} {% include 'snippets/reading_modals/finish_reading_modal.html' with book=active_shelf_book id=modal_id readthrough=readthrough class="" %} -{% join "partially_read" uuid as modal_id %} -{% include 'snippets/reading_modals/partially_read_modal.html' with book=active_shelf_book id=modal_id readthrough=readthrough class="" %} +{% join "stop_reading" uuid as modal_id %} +{% include 'snippets/reading_modals/stop_reading_modal.html' with book=active_shelf_book id=modal_id readthrough=readthrough class="" %} {% join "progress_update" uuid as modal_id %} {% include 'snippets/reading_modals/progress_update_modal.html' with book=active_shelf_book id=modal_id readthrough=readthrough class="" %} diff --git a/bookwyrm/templates/snippets/shelve_button/shelve_button_dropdown_options.html b/bookwyrm/templates/snippets/shelve_button/shelve_button_dropdown_options.html index 23e5f4a44..236c07972 100644 --- a/bookwyrm/templates/snippets/shelve_button/shelve_button_dropdown_options.html +++ b/bookwyrm/templates/snippets/shelve_button/shelve_button_dropdown_options.html @@ -26,11 +26,11 @@ {% join "finish_reading" button_uuid as modal_id %} {% include 'snippets/shelve_button/modal_button.html' with class=class fallback_url=fallback_url %} - {% elif shelf.identifier == 'partially-read' %} + {% elif shelf.identifier == 'stopped-reading' %} - {% trans "Partially read" as button_text %} + {% trans "Stop reading" as button_text %} {% url 'reading-status' 'stop' book.id as fallback_url %} - {% join "partially_read" button_uuid as modal_id %} + {% join "stop_reading" button_uuid as modal_id %} {% include 'snippets/shelve_button/modal_button.html' with class=class fallback_url=fallback_url %} {% elif shelf.identifier == 'to-read' %} diff --git a/bookwyrm/templates/snippets/shelve_button/shelve_button_options.html b/bookwyrm/templates/snippets/shelve_button/shelve_button_options.html index 0a711addd..fb7ee452c 100644 --- a/bookwyrm/templates/snippets/shelve_button/shelve_button_options.html +++ b/bookwyrm/templates/snippets/shelve_button/shelve_button_options.html @@ -33,11 +33,11 @@ {% join "finish_reading" button_uuid as modal_id %} {% include 'snippets/shelve_button/modal_button.html' with class=class fallback_url=fallback_url %} - {% elif shelf.identifier == 'partially-read' %} + {% elif shelf.identifier == 'stopped-reading' %} {% trans "Stop reading" as button_text %} {% url 'reading-status' 'stop' book.id as fallback_url %} - {% join "partially_read" button_uuid as modal_id %} + {% join "stop_reading" button_uuid as modal_id %} {% include 'snippets/shelve_button/modal_button.html' with class=class fallback_url=fallback_url %} {% elif shelf.identifier == 'to-read' %} diff --git a/bookwyrm/templates/snippets/translated_shelf_name.html b/bookwyrm/templates/snippets/translated_shelf_name.html index dc1c579b3..763ca8544 100644 --- a/bookwyrm/templates/snippets/translated_shelf_name.html +++ b/bookwyrm/templates/snippets/translated_shelf_name.html @@ -7,8 +7,8 @@ {% trans "Currently Reading" %} {% elif shelf.identifier == 'read' %} {% trans "Read" %} -{% elif shelf.identifier == 'partially-read' %} - {% trans "Partially Read" %} +{% elif shelf.identifier == 'stopped-reading' %} + {% trans "Stopped Reading" %} {% else %} {{ shelf.name }} {% endif %} diff --git a/bookwyrm/templates/user/user.html b/bookwyrm/templates/user/user.html index 8d7fd31a2..af85159fc 100755 --- a/bookwyrm/templates/user/user.html +++ b/bookwyrm/templates/user/user.html @@ -33,7 +33,7 @@ {% if shelf.name == 'To Read' %}{% trans "To Read" %} {% elif shelf.name == 'Currently Reading' %}{% trans "Currently Reading" %} {% elif shelf.name == 'Read' %}{% trans "Read" %} - {% elif shelf.name == 'Partially Read' %}{% trans "Partially Read" %} + {% elif shelf.name == 'Stopped Reading' %}{% trans "Stopped Reading" %} {% else %}{{ shelf.name }}{% endif %} {% if shelf.size > 4 %}({% blocktrans with size=shelf.size %}View all {{ size }}{% endblocktrans %}){% endif %} diff --git a/bookwyrm/templatetags/shelf_tags.py b/bookwyrm/templatetags/shelf_tags.py index 6c4f59c36..7aef638f4 100644 --- a/bookwyrm/templatetags/shelf_tags.py +++ b/bookwyrm/templatetags/shelf_tags.py @@ -36,19 +36,22 @@ def get_next_shelf(current_shelf): def active_shelf(context, book): """check what shelf a user has a book on, if any""" user = context["request"].user - return cache.get_or_set( - f"active_shelf-{user.id}-{book.id}", - lambda u, b: ( - models.ShelfBook.objects.filter( - shelf__user=u, - book__parent_work__editions=b, - ).first() - or False - ), - user, - book, - timeout=15552000, - ) or {"book": book} + return ( + cache.get_or_set( + f"active_shelf-{user.id}-{book.id}", + lambda u, b: ( + models.ShelfBook.objects.filter( + shelf__user=u, + book__parent_work__editions=b, + ).first() + or False + ), + user, + book, + timeout=15552000, + ) + or {"book": book} + ) @register.simple_tag(takes_context=False) diff --git a/bookwyrm/tests/models/test_user_model.py b/bookwyrm/tests/models/test_user_model.py index 8d3439632..e4395e2f9 100644 --- a/bookwyrm/tests/models/test_user_model.py +++ b/bookwyrm/tests/models/test_user_model.py @@ -58,12 +58,12 @@ class User(TestCase): self.assertTrue("To Read" in names) self.assertTrue("Currently Reading" in names) self.assertTrue("Read" in names) - self.assertTrue("Partially Read" in names) + self.assertTrue("Stopped Reading" in names) ids = [s.identifier for s in shelves] self.assertTrue("to-read" in ids) self.assertTrue("reading" in ids) self.assertTrue("read" in ids) - self.assertTrue("partially-read" in ids) + self.assertTrue("stopped-reading" in ids) def test_activitypub_serialize(self): activity = self.user.to_activity() diff --git a/bookwyrm/views/reading.py b/bookwyrm/views/reading.py index b80bc50df..3cd153d2f 100644 --- a/bookwyrm/views/reading.py +++ b/bookwyrm/views/reading.py @@ -42,7 +42,7 @@ class ReadingStatus(View): "want": models.Shelf.TO_READ, "start": models.Shelf.READING, "finish": models.Shelf.READ_FINISHED, - "stop": models.Shelf.PARTIALLY_READ, + "stop": models.Shelf.STOPPED_READING, }.get(status) if not identifier: return HttpResponseBadRequest() From d63e5ab2d2219aa18c624750b813d60cf425b391 Mon Sep 17 00:00:00 2001 From: Thomas Versteeg Date: Mon, 14 Feb 2022 18:12:08 +0100 Subject: [PATCH 04/85] Fix tests --- .../reading_modals/stop_reading_modal.html | 4 +-- bookwyrm/templatetags/shelf_tags.py | 29 +++++++++---------- bookwyrm/tests/models/test_user_model.py | 2 +- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/bookwyrm/templates/snippets/reading_modals/stop_reading_modal.html b/bookwyrm/templates/snippets/reading_modals/stop_reading_modal.html index 163a34155..93a36da54 100644 --- a/bookwyrm/templates/snippets/reading_modals/stop_reading_modal.html +++ b/bookwyrm/templates/snippets/reading_modals/stop_reading_modal.html @@ -20,10 +20,10 @@ Stop Reading "{{ book_title }}"
-
diff --git a/bookwyrm/templatetags/shelf_tags.py b/bookwyrm/templatetags/shelf_tags.py index 7aef638f4..6c4f59c36 100644 --- a/bookwyrm/templatetags/shelf_tags.py +++ b/bookwyrm/templatetags/shelf_tags.py @@ -36,22 +36,19 @@ def get_next_shelf(current_shelf): def active_shelf(context, book): """check what shelf a user has a book on, if any""" user = context["request"].user - return ( - cache.get_or_set( - f"active_shelf-{user.id}-{book.id}", - lambda u, b: ( - models.ShelfBook.objects.filter( - shelf__user=u, - book__parent_work__editions=b, - ).first() - or False - ), - user, - book, - timeout=15552000, - ) - or {"book": book} - ) + return cache.get_or_set( + f"active_shelf-{user.id}-{book.id}", + lambda u, b: ( + models.ShelfBook.objects.filter( + shelf__user=u, + book__parent_work__editions=b, + ).first() + or False + ), + user, + book, + timeout=15552000, + ) or {"book": book} @register.simple_tag(takes_context=False) diff --git a/bookwyrm/tests/models/test_user_model.py b/bookwyrm/tests/models/test_user_model.py index e4395e2f9..471099ab9 100644 --- a/bookwyrm/tests/models/test_user_model.py +++ b/bookwyrm/tests/models/test_user_model.py @@ -53,7 +53,7 @@ class User(TestCase): def test_user_shelves(self): shelves = models.Shelf.objects.filter(user=self.user).all() - self.assertEqual(len(shelves), 3) + self.assertEqual(len(shelves), 4) names = [s.name for s in shelves] self.assertTrue("To Read" in names) self.assertTrue("Currently Reading" in names) From 5eb113af6b30c71324cafea895bcf1dd5ab7eac0 Mon Sep 17 00:00:00 2001 From: Thomas Versteeg Date: Fri, 25 Feb 2022 22:03:49 +0100 Subject: [PATCH 05/85] Create merge migration --- bookwyrm/migrations/0142_merge_20220225_2103.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 bookwyrm/migrations/0142_merge_20220225_2103.py diff --git a/bookwyrm/migrations/0142_merge_20220225_2103.py b/bookwyrm/migrations/0142_merge_20220225_2103.py new file mode 100644 index 000000000..8053d8f93 --- /dev/null +++ b/bookwyrm/migrations/0142_merge_20220225_2103.py @@ -0,0 +1,14 @@ +# Generated by Django 3.2.12 on 2022-02-25 21:03 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0134_alter_stopped_reading'), + ('bookwyrm', '0141_alter_report_status'), + ] + + operations = [ + ] From 8deee2220e05a0a5fc238780a879cca2b90ceb49 Mon Sep 17 00:00:00 2001 From: Thomas Versteeg Date: Fri, 25 Feb 2022 22:38:58 +0100 Subject: [PATCH 06/85] Fix stopped reading status model in non-javascript environment --- bookwyrm/migrations/0142_merge_20220225_2103.py | 7 +++---- bookwyrm/templates/reading_progress/stop.html | 14 ++++++++++++++ .../reading_modals/stop_reading_modal.html | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 bookwyrm/templates/reading_progress/stop.html diff --git a/bookwyrm/migrations/0142_merge_20220225_2103.py b/bookwyrm/migrations/0142_merge_20220225_2103.py index 8053d8f93..62f8dcfee 100644 --- a/bookwyrm/migrations/0142_merge_20220225_2103.py +++ b/bookwyrm/migrations/0142_merge_20220225_2103.py @@ -6,9 +6,8 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0134_alter_stopped_reading'), - ('bookwyrm', '0141_alter_report_status'), + ("bookwyrm", "0134_alter_stopped_reading"), + ("bookwyrm", "0141_alter_report_status"), ] - operations = [ - ] + operations = [] diff --git a/bookwyrm/templates/reading_progress/stop.html b/bookwyrm/templates/reading_progress/stop.html new file mode 100644 index 000000000..5811c09b6 --- /dev/null +++ b/bookwyrm/templates/reading_progress/stop.html @@ -0,0 +1,14 @@ +{% extends 'layout.html' %} +{% load i18n %} + +{% block title %} +{% blocktrans trimmed with book_title=book.title %} +Stop Reading "{{ book_title }}" +{% endblocktrans %} +{% endblock %} + +{% block content %} + +{% include "snippets/reading_modals/stop_reading_modal.html" with book=book active=True static=True %} + +{% endblock %} diff --git a/bookwyrm/templates/snippets/reading_modals/stop_reading_modal.html b/bookwyrm/templates/snippets/reading_modals/stop_reading_modal.html index 93a36da54..80fb2d5b3 100644 --- a/bookwyrm/templates/snippets/reading_modals/stop_reading_modal.html +++ b/bookwyrm/templates/snippets/reading_modals/stop_reading_modal.html @@ -31,7 +31,7 @@ Stop Reading "{{ book_title }}" - +
From b5119284006ecb0ec02200191940a0a0650afcd1 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Fri, 25 Feb 2022 18:08:30 -0800 Subject: [PATCH 07/85] Create 0134_alter_stopped_reading.py --- .../migrations/0134_alter_stopped_reading.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/bookwyrm/migrations/0134_alter_stopped_reading.py b/bookwyrm/migrations/0134_alter_stopped_reading.py index c0c2c6520..15bd75b21 100644 --- a/bookwyrm/migrations/0134_alter_stopped_reading.py +++ b/bookwyrm/migrations/0134_alter_stopped_reading.py @@ -3,6 +3,22 @@ import bookwyrm.models.fields from django.db import migrations +def add_shelves(apps, schema_editor): + """add any superusers to the "admin" group""" + + db_alias = schema_editor.connection.alias + shelf_model = apps.get_model("bookwyrm", "Shelf") + + users = apps.get_model("bookwyrm", "User") + local_users = users.objects.using(db_alias).filter(local=True) + for user in local_users: + shelf_model.using(db_alias)( + name="Stopped reading", + identifier=Shelf.STOPPED_READING, + user=user, + editable=False, + ).save(broadcast=False) + class Migration(migrations.Migration): @@ -56,4 +72,5 @@ class Migration(migrations.Migration): null=True, ), ), + migrations.RunPython(add_shelves, reverse_code=migrations.RunPython.noop), ] From a5571c65bc127a8767e288fe2f522eb8f2a479bc Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Fri, 25 Feb 2022 18:25:41 -0800 Subject: [PATCH 08/85] Update 0134_alter_stopped_reading.py --- bookwyrm/migrations/0134_alter_stopped_reading.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bookwyrm/migrations/0134_alter_stopped_reading.py b/bookwyrm/migrations/0134_alter_stopped_reading.py index 15bd75b21..13c02e992 100644 --- a/bookwyrm/migrations/0134_alter_stopped_reading.py +++ b/bookwyrm/migrations/0134_alter_stopped_reading.py @@ -3,6 +3,7 @@ import bookwyrm.models.fields from django.db import migrations + def add_shelves(apps, schema_editor): """add any superusers to the "admin" group""" From 1e3f9246d64abb3d6950c4bd0149b2182d2307cf Mon Sep 17 00:00:00 2001 From: Thomas Versteeg Date: Mon, 28 Feb 2022 20:56:59 +0100 Subject: [PATCH 09/85] Produce a proper status --- bookwyrm/views/helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bookwyrm/views/helpers.py b/bookwyrm/views/helpers.py index 74d867b66..b0ee713eb 100644 --- a/bookwyrm/views/helpers.py +++ b/bookwyrm/views/helpers.py @@ -137,6 +137,7 @@ def handle_reading_status(user, shelf, book, privacy): "to-read": "wants to read", "reading": "started reading", "read": "finished reading", + "stopped-reading": "stopped reading", }[shelf.identifier] except KeyError: # it's a non-standard shelf, don't worry about it From 5d8404f79728cae1a0d59f4b6f773d6445a2507a Mon Sep 17 00:00:00 2001 From: Thomas Versteeg Date: Sat, 12 Mar 2022 11:45:09 +0100 Subject: [PATCH 10/85] Add merge migration --- bookwyrm/migrations/0145_merge_20220312_1040.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 bookwyrm/migrations/0145_merge_20220312_1040.py diff --git a/bookwyrm/migrations/0145_merge_20220312_1040.py b/bookwyrm/migrations/0145_merge_20220312_1040.py new file mode 100644 index 000000000..3fa131c44 --- /dev/null +++ b/bookwyrm/migrations/0145_merge_20220312_1040.py @@ -0,0 +1,14 @@ +# Generated by Django 3.2.12 on 2022-03-12 10:40 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0142_merge_20220225_2103'), + ('bookwyrm', '0144_alter_announcement_display_type'), + ] + + operations = [ + ] From b3f03164cc3330cdf170e216021a5a094053c4ce Mon Sep 17 00:00:00 2001 From: Thomas Versteeg Date: Tue, 15 Mar 2022 09:28:33 +0100 Subject: [PATCH 11/85] Apply black --- bookwyrm/migrations/0145_merge_20220312_1040.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bookwyrm/migrations/0145_merge_20220312_1040.py b/bookwyrm/migrations/0145_merge_20220312_1040.py index 3fa131c44..8e6c75ace 100644 --- a/bookwyrm/migrations/0145_merge_20220312_1040.py +++ b/bookwyrm/migrations/0145_merge_20220312_1040.py @@ -6,9 +6,8 @@ from django.db import migrations class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0142_merge_20220225_2103'), - ('bookwyrm', '0144_alter_announcement_display_type'), + ("bookwyrm", "0142_merge_20220225_2103"), + ("bookwyrm", "0144_alter_announcement_display_type"), ] - operations = [ - ] + operations = [] From 819458e82a9d2c276fdf770836131a4b345cdb8a Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 16 Mar 2022 13:53:54 -0700 Subject: [PATCH 12/85] Improves error reporting on activitypub parser --- bookwyrm/activitypub/base_activity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 6bee25f62..92f0c99af 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -65,7 +65,7 @@ class ActivityObject: try: value = kwargs[field.name] if value in (None, MISSING, {}): - raise KeyError() + raise KeyError("Missing required field", field.name) try: is_subclass = issubclass(field.type, ActivityObject) except TypeError: From 159b73d860d3c1e0c7a6bbd636eb232c5ff04bb4 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 16 Mar 2022 13:54:25 -0700 Subject: [PATCH 13/85] Fixes errors in migration --- bookwyrm/migrations/0134_alter_stopped_reading.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/bookwyrm/migrations/0134_alter_stopped_reading.py b/bookwyrm/migrations/0134_alter_stopped_reading.py index 13c02e992..ba49f6120 100644 --- a/bookwyrm/migrations/0134_alter_stopped_reading.py +++ b/bookwyrm/migrations/0134_alter_stopped_reading.py @@ -1,7 +1,8 @@ # Generated by Django 3.2.11 on 2022-02-11 13:19 -import bookwyrm.models.fields from django.db import migrations +import bookwyrm.models.fields +from bookwyrm.models import Shelf def add_shelves(apps, schema_editor): @@ -13,12 +14,14 @@ def add_shelves(apps, schema_editor): users = apps.get_model("bookwyrm", "User") local_users = users.objects.using(db_alias).filter(local=True) for user in local_users: - shelf_model.using(db_alias)( + remote_id = f"{user.remote_id}/books/stopped" + shelf_model.objects.using(db_alias).create( name="Stopped reading", identifier=Shelf.STOPPED_READING, user=user, editable=False, - ).save(broadcast=False) + remote_id=remote_id, + ) class Migration(migrations.Migration): From 108981a226719a041056a8282c49d57be3c1a761 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 16 Mar 2022 16:35:03 -0700 Subject: [PATCH 14/85] Creates fresh migration and removes merges --- bookwyrm/migrations/0142_merge_20220225_2103.py | 13 ------------- bookwyrm/migrations/0145_merge_20220312_1040.py | 13 ------------- ...topped_reading.py => 0146_auto_20220316_2320.py} | 6 +++--- 3 files changed, 3 insertions(+), 29 deletions(-) delete mode 100644 bookwyrm/migrations/0142_merge_20220225_2103.py delete mode 100644 bookwyrm/migrations/0145_merge_20220312_1040.py rename bookwyrm/migrations/{0134_alter_stopped_reading.py => 0146_auto_20220316_2320.py} (95%) diff --git a/bookwyrm/migrations/0142_merge_20220225_2103.py b/bookwyrm/migrations/0142_merge_20220225_2103.py deleted file mode 100644 index 62f8dcfee..000000000 --- a/bookwyrm/migrations/0142_merge_20220225_2103.py +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Django 3.2.12 on 2022-02-25 21:03 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ("bookwyrm", "0134_alter_stopped_reading"), - ("bookwyrm", "0141_alter_report_status"), - ] - - operations = [] diff --git a/bookwyrm/migrations/0145_merge_20220312_1040.py b/bookwyrm/migrations/0145_merge_20220312_1040.py deleted file mode 100644 index 8e6c75ace..000000000 --- a/bookwyrm/migrations/0145_merge_20220312_1040.py +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Django 3.2.12 on 2022-03-12 10:40 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ("bookwyrm", "0142_merge_20220225_2103"), - ("bookwyrm", "0144_alter_announcement_display_type"), - ] - - operations = [] diff --git a/bookwyrm/migrations/0134_alter_stopped_reading.py b/bookwyrm/migrations/0146_auto_20220316_2320.py similarity index 95% rename from bookwyrm/migrations/0134_alter_stopped_reading.py rename to bookwyrm/migrations/0146_auto_20220316_2320.py index ba49f6120..e50bf25ec 100644 --- a/bookwyrm/migrations/0134_alter_stopped_reading.py +++ b/bookwyrm/migrations/0146_auto_20220316_2320.py @@ -1,7 +1,7 @@ -# Generated by Django 3.2.11 on 2022-02-11 13:19 +# Generated by Django 3.2.12 on 2022-03-16 23:20 -from django.db import migrations import bookwyrm.models.fields +from django.db import migrations from bookwyrm.models import Shelf @@ -27,7 +27,7 @@ def add_shelves(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ("bookwyrm", "0133_alter_listitem_notes"), + ("bookwyrm", "0145_sitesettings_version"), ] operations = [ From 71cbe611deb522f8320b9d3a900c6a9608e342fa Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 26 Mar 2022 13:07:27 -0700 Subject: [PATCH 15/85] Merge migration --- bookwyrm/migrations/0148_merge_20220326_2006.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 bookwyrm/migrations/0148_merge_20220326_2006.py diff --git a/bookwyrm/migrations/0148_merge_20220326_2006.py b/bookwyrm/migrations/0148_merge_20220326_2006.py new file mode 100644 index 000000000..978662765 --- /dev/null +++ b/bookwyrm/migrations/0148_merge_20220326_2006.py @@ -0,0 +1,13 @@ +# Generated by Django 3.2.12 on 2022-03-26 20:06 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0146_auto_20220316_2320"), + ("bookwyrm", "0147_alter_user_preferred_language"), + ] + + operations = [] From 3626db3c1a517239ad9029026cd7d26c37714b0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?= Date: Sat, 30 Apr 2022 15:25:09 +0200 Subject: [PATCH 16/85] Add Calibre importer for CSV exports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: André Jaenisch --- bookwyrm/importers/__init__.py | 1 + bookwyrm/importers/calibre_import.py | 8 ++++++++ bookwyrm/templates/import/import.html | 3 +++ bookwyrm/views/imports/import_data.py | 3 +++ 4 files changed, 15 insertions(+) create mode 100644 bookwyrm/importers/calibre_import.py diff --git a/bookwyrm/importers/__init__.py b/bookwyrm/importers/__init__.py index dd3d62e8b..6ce50f160 100644 --- a/bookwyrm/importers/__init__.py +++ b/bookwyrm/importers/__init__.py @@ -1,6 +1,7 @@ """ import classes """ from .importer import Importer +from .calibre_import import CalibreImporter from .goodreads_import import GoodreadsImporter from .librarything_import import LibrarythingImporter from .openlibrary_import import OpenLibraryImporter diff --git a/bookwyrm/importers/calibre_import.py b/bookwyrm/importers/calibre_import.py new file mode 100644 index 000000000..a0d712636 --- /dev/null +++ b/bookwyrm/importers/calibre_import.py @@ -0,0 +1,8 @@ +""" handle reading a csv from calibre """ +from . import Importer + + +class CalibreImporter(Importer): + """csv downloads from OpenLibrary""" + + service = "Calibre" diff --git a/bookwyrm/templates/import/import.html b/bookwyrm/templates/import/import.html index 6df7c0843..fc00389c5 100644 --- a/bookwyrm/templates/import/import.html +++ b/bookwyrm/templates/import/import.html @@ -32,6 +32,9 @@ + diff --git a/bookwyrm/views/imports/import_data.py b/bookwyrm/views/imports/import_data.py index 6e50a14cc..063545895 100644 --- a/bookwyrm/views/imports/import_data.py +++ b/bookwyrm/views/imports/import_data.py @@ -11,6 +11,7 @@ from django.views import View from bookwyrm import forms, models from bookwyrm.importers import ( + CalibreImporter, LibrarythingImporter, GoodreadsImporter, StorygraphImporter, @@ -52,6 +53,8 @@ class Import(View): importer = StorygraphImporter() elif source == "OpenLibrary": importer = OpenLibraryImporter() + elif source == "Calibre": + importer = CalibreImporter() else: # Default : Goodreads importer = GoodreadsImporter() From eeb1cc71975d98566ff9ff79f403bc3ec11cd4f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?= Date: Sat, 30 Apr 2022 19:08:31 +0200 Subject: [PATCH 17/85] Use a default shelf because Calibre indicates no reading status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: André Jaenisch --- bookwyrm/importers/calibre_import.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bookwyrm/importers/calibre_import.py b/bookwyrm/importers/calibre_import.py index a0d712636..18c6358a8 100644 --- a/bookwyrm/importers/calibre_import.py +++ b/bookwyrm/importers/calibre_import.py @@ -3,6 +3,10 @@ from . import Importer class CalibreImporter(Importer): - """csv downloads from OpenLibrary""" + """csv downloads from Calibre""" service = "Calibre" + + def get_shelf(self, normalized_row): + # Calibre export does not indicate which shelf to use. Go with a default one for now + return "to-read" From 6bd9b725e27d4ca850275b111b280c8530aa88c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?= Date: Thu, 5 May 2022 13:07:25 +0200 Subject: [PATCH 18/85] Refactor hard-coded strings with a reference to a static property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: André Jaenisch --- bookwyrm/importers/librarything_import.py | 9 ++++++--- bookwyrm/tests/importers/test_goodreads_import.py | 2 +- bookwyrm/tests/importers/test_importer.py | 8 ++++---- bookwyrm/tests/importers/test_librarything_import.py | 6 +++--- bookwyrm/tests/importers/test_openlibrary_import.py | 2 +- bookwyrm/tests/importers/test_storygraph_import.py | 2 +- 6 files changed, 16 insertions(+), 13 deletions(-) diff --git a/bookwyrm/importers/librarything_import.py b/bookwyrm/importers/librarything_import.py index 37730dee3..c6833547d 100644 --- a/bookwyrm/importers/librarything_import.py +++ b/bookwyrm/importers/librarything_import.py @@ -1,5 +1,8 @@ """ handle reading a tsv from librarything """ import re + +from bookwyrm.models import Shelf + from . import Importer @@ -21,7 +24,7 @@ class LibrarythingImporter(Importer): def get_shelf(self, normalized_row): if normalized_row["date_finished"]: - return "read" + return Shelf.READ_FINISHED if normalized_row["date_started"]: - return "reading" - return "to-read" + return Shelf.READING + return Shelf.TO_READ diff --git a/bookwyrm/tests/importers/test_goodreads_import.py b/bookwyrm/tests/importers/test_goodreads_import.py index 04fb886bf..00c8b981a 100644 --- a/bookwyrm/tests/importers/test_goodreads_import.py +++ b/bookwyrm/tests/importers/test_goodreads_import.py @@ -84,7 +84,7 @@ class GoodreadsImport(TestCase): 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() + shelf = self.local_user.shelf_set.filter(identifier=models.Shelf.READ_FINISHED).first() self.assertIsNone(shelf.books.first()) import_job = self.importer.create_job( diff --git a/bookwyrm/tests/importers/test_importer.py b/bookwyrm/tests/importers/test_importer.py index c8da8a271..5d0b9b005 100644 --- a/bookwyrm/tests/importers/test_importer.py +++ b/bookwyrm/tests/importers/test_importer.py @@ -174,7 +174,7 @@ class GenericImporter(TestCase): def test_handle_imported_book(self, *_): """import added a book, this adds related connections""" - shelf = self.local_user.shelf_set.filter(identifier="read").first() + shelf = self.local_user.shelf_set.filter(identifier=models.Shelf.READ_FINISHED).first() self.assertIsNone(shelf.books.first()) import_job = self.importer.create_job( @@ -193,7 +193,7 @@ class GenericImporter(TestCase): def test_handle_imported_book_already_shelved(self, *_): """import added a book, this adds related connections""" with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"): - shelf = self.local_user.shelf_set.filter(identifier="to-read").first() + shelf = self.local_user.shelf_set.filter(identifier=models.Shelf.TO_READ).first() models.ShelfBook.objects.create( shelf=shelf, user=self.local_user, @@ -217,12 +217,12 @@ class GenericImporter(TestCase): shelf.shelfbook_set.first().shelved_date, make_date(2020, 2, 2) ) self.assertIsNone( - self.local_user.shelf_set.get(identifier="read").books.first() + self.local_user.shelf_set.get(identifier=models.Shelf.READ_FINISHED).books.first() ) def test_handle_import_twice(self, *_): """re-importing books""" - shelf = self.local_user.shelf_set.filter(identifier="read").first() + shelf = self.local_user.shelf_set.filter(identifier=models.Shelf.READ_FINISHED).first() import_job = self.importer.create_job( self.local_user, self.csv, False, "public" ) diff --git a/bookwyrm/tests/importers/test_librarything_import.py b/bookwyrm/tests/importers/test_librarything_import.py index 57d555206..e976027cc 100644 --- a/bookwyrm/tests/importers/test_librarything_import.py +++ b/bookwyrm/tests/importers/test_librarything_import.py @@ -93,7 +93,7 @@ class LibrarythingImport(TestCase): def test_handle_imported_book(self, *_): """librarything import added a book, this adds related connections""" - shelf = self.local_user.shelf_set.filter(identifier="read").first() + shelf = self.local_user.shelf_set.filter(identifier=models.Shelf.READ_FINISHED).first() self.assertIsNone(shelf.books.first()) import_job = self.importer.create_job( @@ -117,7 +117,7 @@ class LibrarythingImport(TestCase): 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.apply_async"): - shelf = self.local_user.shelf_set.filter(identifier="to-read").first() + shelf = self.local_user.shelf_set.filter(identifier=models.Shelf.TO_READ).first() models.ShelfBook.objects.create( shelf=shelf, user=self.local_user, book=self.book ) @@ -135,7 +135,7 @@ class LibrarythingImport(TestCase): shelf.refresh_from_db() self.assertEqual(shelf.books.first(), self.book) self.assertIsNone( - self.local_user.shelf_set.get(identifier="read").books.first() + self.local_user.shelf_set.get(identifier=models.Shelf.READ_FINISHED).books.first() ) readthrough = models.ReadThrough.objects.get(user=self.local_user) diff --git a/bookwyrm/tests/importers/test_openlibrary_import.py b/bookwyrm/tests/importers/test_openlibrary_import.py index a775c5969..dbd177b86 100644 --- a/bookwyrm/tests/importers/test_openlibrary_import.py +++ b/bookwyrm/tests/importers/test_openlibrary_import.py @@ -70,7 +70,7 @@ class OpenLibraryImport(TestCase): def test_handle_imported_book(self, *_): """openlibrary import added a book, this adds related connections""" - shelf = self.local_user.shelf_set.filter(identifier="reading").first() + shelf = self.local_user.shelf_set.filter(identifier=models.Shelf.READING).first() self.assertIsNone(shelf.books.first()) import_job = self.importer.create_job( diff --git a/bookwyrm/tests/importers/test_storygraph_import.py b/bookwyrm/tests/importers/test_storygraph_import.py index 670c6e5e4..69788d60a 100644 --- a/bookwyrm/tests/importers/test_storygraph_import.py +++ b/bookwyrm/tests/importers/test_storygraph_import.py @@ -62,7 +62,7 @@ class StorygraphImport(TestCase): def test_handle_imported_book(self, *_): """storygraph import added a book, this adds related connections""" - shelf = self.local_user.shelf_set.filter(identifier="to-read").first() + shelf = self.local_user.shelf_set.filter(identifier=models.Shelf.TO_READ).first() self.assertIsNone(shelf.books.first()) import_job = self.importer.create_job( From 22fcb61fb23654028765431dda4405cd6d132869 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?= Date: Thu, 5 May 2022 13:08:01 +0200 Subject: [PATCH 19/85] Write tests for Calibre importer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: André Jaenisch --- bookwyrm/importers/calibre_import.py | 4 +- bookwyrm/tests/data/calibre.csv | 2 + .../tests/importers/test_calibre_import.py | 67 +++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 bookwyrm/tests/data/calibre.csv create mode 100644 bookwyrm/tests/importers/test_calibre_import.py diff --git a/bookwyrm/importers/calibre_import.py b/bookwyrm/importers/calibre_import.py index 18c6358a8..ce20fbfa1 100644 --- a/bookwyrm/importers/calibre_import.py +++ b/bookwyrm/importers/calibre_import.py @@ -1,4 +1,6 @@ """ handle reading a csv from calibre """ +from bookwyrm.models import Shelf + from . import Importer @@ -9,4 +11,4 @@ class CalibreImporter(Importer): def get_shelf(self, normalized_row): # Calibre export does not indicate which shelf to use. Go with a default one for now - return "to-read" + return Shelf.TO_READ diff --git a/bookwyrm/tests/data/calibre.csv b/bookwyrm/tests/data/calibre.csv new file mode 100644 index 000000000..4f936cfa9 --- /dev/null +++ b/bookwyrm/tests/data/calibre.csv @@ -0,0 +1,2 @@ +authors,author_sort,rating,library_name,timestamp,formats,size,isbn,identifiers,comments,tags,series,series_index,languages,title,cover,title_sort,publisher,pubdate,id,uuid +"Seanan McGuire","McGuire, Seanan","5","Bücher","2021-01-19T22:41:16+01:00","epub, original_epub","1433809","9780756411800","goodreads:39077187,isbn:9780756411800","REPLACED COMMENTS (BOOK DESCRIPTION) BECAUSE IT IS REALLY LONG.","Cryptids, Fantasy, Romance, Magic","InCryptid","8.0","eng","That Ain't Witchcraft","/home/tastytea/Bücher/Seanan McGuire/That Ain't Witchcraft (864)/cover.jpg","That Ain't Witchcraft","Daw Books","2019-03-05T01:00:00+01:00","864","3051ed45-8943-4900-a22a-d2704e3583df" diff --git a/bookwyrm/tests/importers/test_calibre_import.py b/bookwyrm/tests/importers/test_calibre_import.py new file mode 100644 index 000000000..5257caa63 --- /dev/null +++ b/bookwyrm/tests/importers/test_calibre_import.py @@ -0,0 +1,67 @@ +""" testing import """ +import pathlib +from unittest.mock import patch + +from django.test import TestCase + +from bookwyrm import models +from bookwyrm.importers import CalibreImporter +from bookwyrm.importers.importer import handle_imported_book + + +# pylint: disable=consider-using-with +@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") +@patch("bookwyrm.activitystreams.populate_stream_task.delay") +@patch("bookwyrm.activitystreams.add_book_statuses_task.delay") +class CalibreImport(TestCase): + """importing from Calibre csv""" + + def setUp(self): + """use a test csv""" + self.importer = CalibreImporter() + datafile = pathlib.Path(__file__).parent.joinpath("../data/calibre.csv") + self.csv = open(datafile, "r", encoding=self.importer.encoding) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch( + "bookwyrm.activitystreams.populate_stream_task.delay" + ), patch("bookwyrm.lists_stream.populate_lists_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse", "mouse@mouse.mouse", "password", local=True + ) + + 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_create_job(self, *_): + """creates the import job entry and checks csv""" + import_job = self.importer.create_job( + self.local_user, self.csv, False, "public" + ) + + import_items = ( + models.ImportItem.objects.filter(job=import_job).order_by("index").all() + ) + self.assertEqual(len(import_items), 1) + self.assertEqual(import_items[0].index, 0) + self.assertEqual(import_items[0].normalized_data["title"], "That Ain't Witchcraft") + + def test_handle_imported_book(self, *_): + """calibre import added a book, this adds related connections""" + shelf = self.local_user.shelf_set.filter(identifier=models.Shelf.TO_READ).first() + self.assertIsNone(shelf.books.first()) + + 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) From 62c7661fb92c282d72a0d1ff5bc8509bd1635ad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?= Date: Thu, 5 May 2022 21:31:56 +0200 Subject: [PATCH 20/85] Reformat tests using black MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: André Jaenisch --- bookwyrm/tests/importers/test_calibre_import.py | 8 ++++++-- .../tests/importers/test_goodreads_import.py | 4 +++- bookwyrm/tests/importers/test_importer.py | 16 ++++++++++++---- .../tests/importers/test_librarything_import.py | 12 +++++++++--- .../tests/importers/test_openlibrary_import.py | 4 +++- .../tests/importers/test_storygraph_import.py | 4 +++- 6 files changed, 36 insertions(+), 12 deletions(-) diff --git a/bookwyrm/tests/importers/test_calibre_import.py b/bookwyrm/tests/importers/test_calibre_import.py index 5257caa63..aff44a241 100644 --- a/bookwyrm/tests/importers/test_calibre_import.py +++ b/bookwyrm/tests/importers/test_calibre_import.py @@ -46,11 +46,15 @@ class CalibreImport(TestCase): ) self.assertEqual(len(import_items), 1) self.assertEqual(import_items[0].index, 0) - self.assertEqual(import_items[0].normalized_data["title"], "That Ain't Witchcraft") + self.assertEqual( + import_items[0].normalized_data["title"], "That Ain't Witchcraft" + ) def test_handle_imported_book(self, *_): """calibre import added a book, this adds related connections""" - shelf = self.local_user.shelf_set.filter(identifier=models.Shelf.TO_READ).first() + shelf = self.local_user.shelf_set.filter( + identifier=models.Shelf.TO_READ + ).first() self.assertIsNone(shelf.books.first()) import_job = self.importer.create_job( diff --git a/bookwyrm/tests/importers/test_goodreads_import.py b/bookwyrm/tests/importers/test_goodreads_import.py index 00c8b981a..9d1c19085 100644 --- a/bookwyrm/tests/importers/test_goodreads_import.py +++ b/bookwyrm/tests/importers/test_goodreads_import.py @@ -84,7 +84,9 @@ class GoodreadsImport(TestCase): def test_handle_imported_book(self, *_): """goodreads import added a book, this adds related connections""" - shelf = self.local_user.shelf_set.filter(identifier=models.Shelf.READ_FINISHED).first() + shelf = self.local_user.shelf_set.filter( + identifier=models.Shelf.READ_FINISHED + ).first() self.assertIsNone(shelf.books.first()) import_job = self.importer.create_job( diff --git a/bookwyrm/tests/importers/test_importer.py b/bookwyrm/tests/importers/test_importer.py index 5d0b9b005..c12095a4c 100644 --- a/bookwyrm/tests/importers/test_importer.py +++ b/bookwyrm/tests/importers/test_importer.py @@ -174,7 +174,9 @@ class GenericImporter(TestCase): def test_handle_imported_book(self, *_): """import added a book, this adds related connections""" - shelf = self.local_user.shelf_set.filter(identifier=models.Shelf.READ_FINISHED).first() + shelf = self.local_user.shelf_set.filter( + identifier=models.Shelf.READ_FINISHED + ).first() self.assertIsNone(shelf.books.first()) import_job = self.importer.create_job( @@ -193,7 +195,9 @@ class GenericImporter(TestCase): def test_handle_imported_book_already_shelved(self, *_): """import added a book, this adds related connections""" with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"): - shelf = self.local_user.shelf_set.filter(identifier=models.Shelf.TO_READ).first() + shelf = self.local_user.shelf_set.filter( + identifier=models.Shelf.TO_READ + ).first() models.ShelfBook.objects.create( shelf=shelf, user=self.local_user, @@ -217,12 +221,16 @@ class GenericImporter(TestCase): shelf.shelfbook_set.first().shelved_date, make_date(2020, 2, 2) ) self.assertIsNone( - self.local_user.shelf_set.get(identifier=models.Shelf.READ_FINISHED).books.first() + self.local_user.shelf_set.get( + identifier=models.Shelf.READ_FINISHED + ).books.first() ) def test_handle_import_twice(self, *_): """re-importing books""" - shelf = self.local_user.shelf_set.filter(identifier=models.Shelf.READ_FINISHED).first() + shelf = self.local_user.shelf_set.filter( + identifier=models.Shelf.READ_FINISHED + ).first() import_job = self.importer.create_job( self.local_user, self.csv, False, "public" ) diff --git a/bookwyrm/tests/importers/test_librarything_import.py b/bookwyrm/tests/importers/test_librarything_import.py index e976027cc..3994e8cde 100644 --- a/bookwyrm/tests/importers/test_librarything_import.py +++ b/bookwyrm/tests/importers/test_librarything_import.py @@ -93,7 +93,9 @@ class LibrarythingImport(TestCase): def test_handle_imported_book(self, *_): """librarything import added a book, this adds related connections""" - shelf = self.local_user.shelf_set.filter(identifier=models.Shelf.READ_FINISHED).first() + shelf = self.local_user.shelf_set.filter( + identifier=models.Shelf.READ_FINISHED + ).first() self.assertIsNone(shelf.books.first()) import_job = self.importer.create_job( @@ -117,7 +119,9 @@ class LibrarythingImport(TestCase): 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.apply_async"): - shelf = self.local_user.shelf_set.filter(identifier=models.Shelf.TO_READ).first() + shelf = self.local_user.shelf_set.filter( + identifier=models.Shelf.TO_READ + ).first() models.ShelfBook.objects.create( shelf=shelf, user=self.local_user, book=self.book ) @@ -135,7 +139,9 @@ class LibrarythingImport(TestCase): shelf.refresh_from_db() self.assertEqual(shelf.books.first(), self.book) self.assertIsNone( - self.local_user.shelf_set.get(identifier=models.Shelf.READ_FINISHED).books.first() + self.local_user.shelf_set.get( + identifier=models.Shelf.READ_FINISHED + ).books.first() ) readthrough = models.ReadThrough.objects.get(user=self.local_user) diff --git a/bookwyrm/tests/importers/test_openlibrary_import.py b/bookwyrm/tests/importers/test_openlibrary_import.py index dbd177b86..28c10e50c 100644 --- a/bookwyrm/tests/importers/test_openlibrary_import.py +++ b/bookwyrm/tests/importers/test_openlibrary_import.py @@ -70,7 +70,9 @@ class OpenLibraryImport(TestCase): def test_handle_imported_book(self, *_): """openlibrary import added a book, this adds related connections""" - shelf = self.local_user.shelf_set.filter(identifier=models.Shelf.READING).first() + shelf = self.local_user.shelf_set.filter( + identifier=models.Shelf.READING + ).first() self.assertIsNone(shelf.books.first()) import_job = self.importer.create_job( diff --git a/bookwyrm/tests/importers/test_storygraph_import.py b/bookwyrm/tests/importers/test_storygraph_import.py index 69788d60a..afff0b218 100644 --- a/bookwyrm/tests/importers/test_storygraph_import.py +++ b/bookwyrm/tests/importers/test_storygraph_import.py @@ -62,7 +62,9 @@ class StorygraphImport(TestCase): def test_handle_imported_book(self, *_): """storygraph import added a book, this adds related connections""" - shelf = self.local_user.shelf_set.filter(identifier=models.Shelf.TO_READ).first() + shelf = self.local_user.shelf_set.filter( + identifier=models.Shelf.TO_READ + ).first() self.assertIsNone(shelf.books.first()) import_job = self.importer.create_job( From 876d9c2695cd3a0d4d1a345e56edeaae8429becc Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 16 May 2022 09:24:01 -0700 Subject: [PATCH 21/85] Fixes how backdated statuses are prioritized --- bookwyrm/activitystreams.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bookwyrm/activitystreams.py b/bookwyrm/activitystreams.py index f2dd43fb2..a90d7943b 100644 --- a/bookwyrm/activitystreams.py +++ b/bookwyrm/activitystreams.py @@ -298,8 +298,9 @@ def add_status_on_create_command(sender, instance, 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: + if instance.published_date < timezone.now() - timedelta( + days=1 + ) or instance.created_date < instance.published_date - timedelta(days=1): priority = LOW add_status_task.apply_async( From fdd4691e007c33867fbd4a4ddb27a1e0b69ad754 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 16 May 2022 09:41:34 -0700 Subject: [PATCH 22/85] Adds unit test --- .../tests/activitystreams/test_signals.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/bookwyrm/tests/activitystreams/test_signals.py b/bookwyrm/tests/activitystreams/test_signals.py index 4db1875f9..f7c6c20bb 100644 --- a/bookwyrm/tests/activitystreams/test_signals.py +++ b/bookwyrm/tests/activitystreams/test_signals.py @@ -1,6 +1,10 @@ """ testing activitystreams """ +from datetime import datetime, timedelta from unittest.mock import patch + from django.test import TestCase +from django.utils import timezone + from bookwyrm import activitystreams, models @@ -62,6 +66,39 @@ class ActivitystreamsSignals(TestCase): self.assertEqual(args["args"][0], status.id) self.assertEqual(args["queue"], "high_priority") + def test_add_status_on_create_created_low_priority(self, *_): + """a new statuses has entered""" + # created later than publication + status = models.Status.objects.create( + user=self.remote_user, + content="hi", + privacy="public", + created_date=datetime(2022, 5, 16, tzinfo=timezone.utc), + published_date=datetime(2022, 5, 14, tzinfo=timezone.utc), + ) + 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[1] + self.assertEqual(args["args"][0], status.id) + self.assertEqual(args["queue"], "low_priority") + + # published later than yesterday + status = models.Status.objects.create( + user=self.remote_user, + content="hi", + privacy="public", + published_date=timezone.now() - timedelta(days=1), + ) + 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[1] + self.assertEqual(args["args"][0], status.id) + self.assertEqual(args["queue"], "low_priority") + def test_populate_streams_on_account_create_command(self, *_): """create streams for a user""" with patch("bookwyrm.activitystreams.populate_stream_task.delay") as mock: From fd43b56d31ebce09f11e42de8fb1109b7eda1e71 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 16 May 2022 10:11:45 -0700 Subject: [PATCH 23/85] Fixes celery error encountering Article type activities --- bookwyrm/activitypub/base_activity.py | 2 +- .../tests/views/inbox/test_inbox_create.py | 32 +++++++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 6bee25f62..1aacfd1f9 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -43,7 +43,7 @@ def naive_parse(activity_objects, activity_json, serializer=None): serializer = activity_objects[activity_type] except KeyError as err: # we know this exists and that we can't handle it - if activity_type in ["Question"]: + if activity_type in ["Question", "Article"]: return None raise ActivitySerializerError(err) diff --git a/bookwyrm/tests/views/inbox/test_inbox_create.py b/bookwyrm/tests/views/inbox/test_inbox_create.py index 4ee366cfe..d08840b5b 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_create.py +++ b/bookwyrm/tests/views/inbox/test_inbox_create.py @@ -208,16 +208,44 @@ class InboxCreate(TestCase): self.assertEqual(book_list.description, "summary text") self.assertEqual(book_list.remote_id, "https://example.com/list/22") - def test_create_unsupported_type(self, *_): + def test_create_unsupported_type_question(self, *_): """ignore activities we know we can't handle""" activity = self.create_json activity["object"] = { "id": "https://example.com/status/887", "type": "Question", } - # just observer how it doesn't throw an error + # just observe how it doesn't throw an error views.inbox.activity_task(activity) + def test_create_unsupported_type_article(self, *_): + """ special case in unsupported type because we do know what it is""" + activity = self.create_json + activity["object"] = { + "id": "https://example.com/status/887", + "type": "Article", + "name": "hello", + "published": "2021-04-29T21:27:30.014235+00:00", + "attributedTo": "https://example.com/user/mouse", + "to": ["https://www.w3.org/ns/activitystreams#Public"], + "cc": ["https://example.com/user/mouse/followers"], + "sensitive": False, + "@context": "https://www.w3.org/ns/activitystreams", + } + # just observe how it doesn't throw an error + views.inbox.activity_task(activity) + + def test_create_unsupported_type_unknown(self, *_): + """Something truly unexpected should throw an error""" + activity = self.create_json + activity["object"] = { + "id": "https://example.com/status/887", + "type": "Blaaaah", + } + # error this time + with self.assertRaises(ActivitySerializerError): + views.inbox.activity_task(activity) + def test_create_unknown_type(self, *_): """ignore activities we know we've never heard of""" activity = self.create_json From b2775c51606b9f7dba2983f4d8d7f57a9b28b5d8 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 16 May 2022 10:20:13 -0700 Subject: [PATCH 24/85] Check unsupported types before attempting to serialize --- bookwyrm/activitypub/base_activity.py | 4 ++-- bookwyrm/tests/views/inbox/test_inbox_create.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 1aacfd1f9..448d55637 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -39,12 +39,12 @@ def naive_parse(activity_objects, activity_json, serializer=None): activity_json["type"] = "PublicKey" activity_type = activity_json.get("type") + if activity_type in ["Question", "Article"]: + return None try: serializer = activity_objects[activity_type] except KeyError as err: # we know this exists and that we can't handle it - if activity_type in ["Question", "Article"]: - return None raise ActivitySerializerError(err) return serializer(activity_objects=activity_objects, **activity_json) diff --git a/bookwyrm/tests/views/inbox/test_inbox_create.py b/bookwyrm/tests/views/inbox/test_inbox_create.py index d08840b5b..cc1b8d508 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_create.py +++ b/bookwyrm/tests/views/inbox/test_inbox_create.py @@ -219,7 +219,7 @@ class InboxCreate(TestCase): views.inbox.activity_task(activity) def test_create_unsupported_type_article(self, *_): - """ special case in unsupported type because we do know what it is""" + """special case in unsupported type because we do know what it is""" activity = self.create_json activity["object"] = { "id": "https://example.com/status/887", From 8d2da587d990c03c131b3bf8ab7e2d65e370c42b Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 16 May 2022 11:06:11 -0700 Subject: [PATCH 25/85] Prevent error when a book language has a null value --- bookwyrm/models/book.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index 3ea8e1a8e..190046019 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -176,8 +176,8 @@ class Book(BookDataModel): """properties of this edition, as a string""" items = [ self.physical_format if hasattr(self, "physical_format") else None, - self.languages[0] + " language" - if self.languages and self.languages[0] != "English" + f"{self.languages[0]} language" + if self.languages and self.languages[0] and self.languages[0] != "English" else None, str(self.published_date.year) if self.published_date else None, ", ".join(self.publishers) if hasattr(self, "publishers") else None, From 6d7bb336838d0fae953a76e2b0d6506eac1ab0c0 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 19 May 2022 09:32:01 -0700 Subject: [PATCH 26/85] Fixes urls in edit book form --- bookwyrm/templates/book/edit/edit_book.html | 12 ++++++++++-- bookwyrm/urls.py | 12 ++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/bookwyrm/templates/book/edit/edit_book.html b/bookwyrm/templates/book/edit/edit_book.html index b088c1e87..e5b865b55 100644 --- a/bookwyrm/templates/book/edit/edit_book.html +++ b/bookwyrm/templates/book/edit/edit_book.html @@ -41,10 +41,18 @@ class="block" {% if book.id %} name="edit-book" - action="{{ book.local_path }}/{% if confirm_mode %}confirm{% else %}edit{% endif %}" + {% if confirm_mode %} + action="{% url 'edit-book-confirm' book.id %}" + {% else %} + action="{% url 'edit-book' book.id %}" + {% endif %} {% else %} name="create-book" - action="/create-book{% if confirm_mode %}/confirm{% endif %}" + {% if confirm_mode %} + action="{% url 'create-book-confirm' %}" + {% else %} + action="{% url 'create-book' %}" + {% endif %} {% endif %} method="post" enctype="multipart/form-data" diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 9b2b68f1b..bb4cbbe2e 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -537,12 +537,20 @@ urlpatterns = [ name="book-user-statuses", ), re_path(rf"{BOOK_PATH}/edit/?$", views.EditBook.as_view(), name="edit-book"), - re_path(rf"{BOOK_PATH}/confirm/?$", views.ConfirmEditBook.as_view()), + re_path( + rf"{BOOK_PATH}/confirm/?$", + views.ConfirmEditBook.as_view(), + name="edit-book-confirm", + ), re_path( r"^create-book/data/?$", views.create_book_from_data, name="create-book-data" ), re_path(r"^create-book/?$", views.CreateBook.as_view(), name="create-book"), - re_path(r"^create-book/confirm/?$", views.ConfirmEditBook.as_view()), + re_path( + r"^create-book/confirm/?$", + views.ConfirmEditBook.as_view(), + name="create-book-confirm", + ), re_path(rf"{BOOK_PATH}/editions(.json)?/?$", views.Editions.as_view()), re_path( r"^upload-cover/(?P\d+)/?$", views.upload_cover, name="upload-cover" From d8b2ab74d1469fd40fcca4c3626832b1a48b3737 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 23 May 2022 11:08:04 -0700 Subject: [PATCH 27/85] Fixes edit author paths --- bookwyrm/templates/author/edit_author.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/templates/author/edit_author.html b/bookwyrm/templates/author/edit_author.html index 6f72b8700..b0727c43b 100644 --- a/bookwyrm/templates/author/edit_author.html +++ b/bookwyrm/templates/author/edit_author.html @@ -24,7 +24,7 @@ {% endif %} - + {% csrf_token %} From 12541d5f1c46e7e074b51d19b2b8fd90b9f3e7f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?= Date: Mon, 23 May 2022 20:52:26 +0200 Subject: [PATCH 28/85] Map timestamp to date_added to avoid integrity error. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: André Jaenisch --- bookwyrm/importers/calibre_import.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/bookwyrm/importers/calibre_import.py b/bookwyrm/importers/calibre_import.py index ce20fbfa1..b9eb3da7d 100644 --- a/bookwyrm/importers/calibre_import.py +++ b/bookwyrm/importers/calibre_import.py @@ -9,6 +9,20 @@ class CalibreImporter(Importer): service = "Calibre" + def __init__(self, *args, **kwargs): + # Add timestamp to row_mappings_guesses for date_added to avoid + # integrity error + row_mappings_guesses = [] + + for field, mapping in self.row_mappings_guesses: + if field in ('date_added',): + row_mappings_guesses.append((field, mapping + ['timestamp'])) + else: + row_mappings_guesses.append((field, mapping)) + + self.row_mappings_guesses = row_mappings_guesses + super().__init__(*args, **kwargs) + def get_shelf(self, normalized_row): # Calibre export does not indicate which shelf to use. Go with a default one for now return Shelf.TO_READ From b564e514fdc1afc35ececdaf1e45b384e8d2c6d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?= Date: Mon, 23 May 2022 20:52:57 +0200 Subject: [PATCH 29/85] Handle parsed dates that already have a timezone on import. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: André Jaenisch --- bookwyrm/models/import_job.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/bookwyrm/models/import_job.py b/bookwyrm/models/import_job.py index bcba391b6..7ebac0e19 100644 --- a/bookwyrm/models/import_job.py +++ b/bookwyrm/models/import_job.py @@ -175,9 +175,12 @@ class ImportItem(models.Model): def date_added(self): """when the book was added to this dataset""" if self.normalized_data.get("date_added"): - return timezone.make_aware( - dateutil.parser.parse(self.normalized_data.get("date_added")) - ) + parsed_date_added = dateutil.parser.parse(self.normalized_data.get("date_added")) + if timezone.is_aware(parsed_date_added): + # Keep timezone if import already had one + return parsed_date_added + + return timezone.make_aware(parsed_date_added) return None @property From d837146b660334845f123dbef52264253c02fc9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Jaenisch?= Date: Mon, 23 May 2022 20:59:28 +0200 Subject: [PATCH 30/85] Make black happy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: André Jaenisch --- bookwyrm/importers/calibre_import.py | 4 ++-- bookwyrm/models/import_job.py | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/bookwyrm/importers/calibre_import.py b/bookwyrm/importers/calibre_import.py index b9eb3da7d..7395e2f7b 100644 --- a/bookwyrm/importers/calibre_import.py +++ b/bookwyrm/importers/calibre_import.py @@ -15,8 +15,8 @@ class CalibreImporter(Importer): row_mappings_guesses = [] for field, mapping in self.row_mappings_guesses: - if field in ('date_added',): - row_mappings_guesses.append((field, mapping + ['timestamp'])) + if field in ("date_added",): + row_mappings_guesses.append((field, mapping + ["timestamp"])) else: row_mappings_guesses.append((field, mapping)) diff --git a/bookwyrm/models/import_job.py b/bookwyrm/models/import_job.py index 7ebac0e19..556f133f9 100644 --- a/bookwyrm/models/import_job.py +++ b/bookwyrm/models/import_job.py @@ -175,7 +175,10 @@ class ImportItem(models.Model): def date_added(self): """when the book was added to this dataset""" if self.normalized_data.get("date_added"): - parsed_date_added = dateutil.parser.parse(self.normalized_data.get("date_added")) + parsed_date_added = dateutil.parser.parse( + self.normalized_data.get("date_added") + ) + if timezone.is_aware(parsed_date_added): # Keep timezone if import already had one return parsed_date_added From ae2006c726829b4a1ef466218c7b1066f27dc665 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 23 May 2022 12:46:45 -0700 Subject: [PATCH 31/85] Updates black version --- dev-tools/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-tools/requirements.txt b/dev-tools/requirements.txt index 5719da1c9..a84bf64b2 100644 --- a/dev-tools/requirements.txt +++ b/dev-tools/requirements.txt @@ -1 +1 @@ -black==22.1.0 +black==22.3.0 From efd1fd82a9d36a274a4643a5fc1b5ce944dc377d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 23 May 2022 13:02:06 -0700 Subject: [PATCH 32/85] Corrects redirect to confirm mode when adding book --- bookwyrm/views/books/edit_book.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bookwyrm/views/books/edit_book.py b/bookwyrm/views/books/edit_book.py index 2315cfce2..87651fbef 100644 --- a/bookwyrm/views/books/edit_book.py +++ b/bookwyrm/views/books/edit_book.py @@ -115,6 +115,7 @@ class CreateBook(View): # go to confirm mode if not parent_work_id or data.get("add_author"): + data["confirm_mode"] = True return TemplateResponse(request, "book/edit/edit_book.html", data) with transaction.atomic(): From b2c587e082c5f32630bf76d167075de824cbd45c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 23 May 2022 13:32:23 -0700 Subject: [PATCH 33/85] Adds unit test for add author code when editing book --- bookwyrm/tests/views/books/test_edit_book.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/bookwyrm/tests/views/books/test_edit_book.py b/bookwyrm/tests/views/books/test_edit_book.py index cabfe972d..31f0e63b7 100644 --- a/bookwyrm/tests/views/books/test_edit_book.py +++ b/bookwyrm/tests/views/books/test_edit_book.py @@ -9,6 +9,7 @@ from django.test import TestCase from django.test.client import RequestFactory from bookwyrm import forms, models, views +from bookwyrm.views.books.edit_book import add_authors from bookwyrm.tests.validate_html import validate_html from bookwyrm.tests.views.books.test_book import _setup_cover_url @@ -214,3 +215,22 @@ class EditBookViews(TestCase): self.book.refresh_from_db() self.assertTrue(self.book.cover) + + def test_add_authors_helper(self): + """converts form input into author matches""" + form = forms.EditionForm(instance=self.book) + form.data["title"] = "New Title" + form.data["last_edited_by"] = self.local_user.id + form.data["add_author"] = ["Sappho", "Some Guy"] + request = self.factory.post("", form.data) + request.user = self.local_user + + with patch("bookwyrm.utils.isni.find_authors_by_name") as mock: + mock.return_value = [] + result = add_authors(request, form.data) + + self.assertTrue(result["confirm_mode"]) + self.assertEqual(result["add_author"], ["Sappho", "Some Guy"]) + self.assertEqual(len(result["author_matches"]), 2) + self.assertEqual(result["author_matches"][0]["name"], "Sappho") + self.assertEqual(result["author_matches"][2]["name"], "Some Guy") From 69f192e78c861d835e3f5ddb7891a30becd5631a Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 23 May 2022 13:52:10 -0700 Subject: [PATCH 34/85] Fixes error in add author code returning too soon --- bookwyrm/tests/views/books/test_edit_book.py | 2 +- bookwyrm/views/books/edit_book.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/tests/views/books/test_edit_book.py b/bookwyrm/tests/views/books/test_edit_book.py index 31f0e63b7..c7869807b 100644 --- a/bookwyrm/tests/views/books/test_edit_book.py +++ b/bookwyrm/tests/views/books/test_edit_book.py @@ -233,4 +233,4 @@ class EditBookViews(TestCase): self.assertEqual(result["add_author"], ["Sappho", "Some Guy"]) self.assertEqual(len(result["author_matches"]), 2) self.assertEqual(result["author_matches"][0]["name"], "Sappho") - self.assertEqual(result["author_matches"][2]["name"], "Some Guy") + self.assertEqual(result["author_matches"][1]["name"], "Some Guy") diff --git a/bookwyrm/views/books/edit_book.py b/bookwyrm/views/books/edit_book.py index 2315cfce2..d830ebdcf 100644 --- a/bookwyrm/views/books/edit_book.py +++ b/bookwyrm/views/books/edit_book.py @@ -189,7 +189,7 @@ def add_authors(request, data): "existing_isnis": exists, } ) - return data + return data @require_POST From 867981b2a4e6c4ed441a09a7824dbb19d9719b24 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 23 May 2022 15:20:35 -0700 Subject: [PATCH 35/85] Updates locales (changes to German, Romanian) --- locale/de_DE/LC_MESSAGES/django.mo | Bin 89936 -> 30883 bytes locale/en_US/LC_MESSAGES/django.po | 56 ++++++++++++++--------------- locale/ro_RO/LC_MESSAGES/django.mo | Bin 93223 -> 94710 bytes locale/ro_RO/LC_MESSAGES/django.po | 18 +++++----- 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/locale/de_DE/LC_MESSAGES/django.mo b/locale/de_DE/LC_MESSAGES/django.mo index 1ec614a46c72a11a9fb8b450af1486f02cf0bba7..4ce83f72b3f3850c58528f13afcd23442a75a515 100644 GIT binary patch literal 30883 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 literal 89936 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%0Dhi<&yrLGgo&d7X7vwt7_LdUR z?ujbYMF~-$J{Wj8G}796aATYyJT(t=K&S$IRWm5vAb1d+pe28xHleO-5$K>yQ11a0 z7oeuCLL#_XmYM^asRhl`BIbwTMuwqnSBAC>p!F798F=?VG3KiMOwfv8)D6$5CVFAb<+=zlK3xnN=1>4R3cSz( zI-sS;fNTa_7kGjlq_#8;}}Km=@`E69kF(qeFr2Rg}*XLyZBY*%HuNZa5!pJfZ}>R}5=s!!=kj zfO{OEjs^HQ7AsIxfZA4h456UKOW^(0pjDcn#Kz!}SfY?tng>2Z2(&d!kpZ$oJUb_` zND*xm2hkn{olaE*(q9B>V1UXn@UT71Gnj6MorZwxL?LjS6FjI4JsSqH$c zUUUqdas~BaQTK6yjsSq2Bm+BMKoPpJ1?&XKsWzZt3ef33#h@i7P@iRjj;hbe0Zj&g zj(|y3fNq+B&W}T*9x@RDP8X1dj*dcJX%VOa2--YX44NKIRR9|c)&NlswV_l2l!(&u zi&8)rQGh~72ehFqH8T&kt*fdOer_6QUepmD9Iyj6L6b}H!-^R|t7Q=vHz1l|sv59v z9-=7)TK|)n3$iaS7351qT)_@k0fjE46^d9w2QD4K_t<2D+M(c?XYitSkVIlJxb#ef z7or}ZB|YE?%RIzXFLV|}5mvFM>VOCJVPj_CgY`faK4|e!X|6&}esNVPq^XP5IFy-V z(5i=Y&{0&N9XgOTD_He|5;ka+AYx)D6*AZhn!*8ZhRRG$2S-?ODrv{CK+_gvP8OUj z;OAB9DL_{7f#w`Qbs?yy0xDdf3ol^juS3W7z-!6#vO&kaLynlq1f8;wnFl-AFSED= z=0IpqDmAYJa%2#sUkh$%fVNYD&zghGd4e6330_K^nyUa_-va7?--L5K~e?<#}E< zD79o1l_*q|S}DNJJ_Xfp@!-CfjXtRHXs2hBRAi^%3p>^aR4An67lD?a!Ydo-{$8k| zpe|4z(lMK$)RC(YkY7+*pbMP<1p5)@2=GBgsksWESq;$UE6CDG1y~(~Xcs^$DDcE1 zbU;*3!3}&Mc`B^I3fp!K3MbHV$u!UmC#d{N&HzO%Yz7~klcAk{(1HcfgjNpdY8hzw zK${?-x-b_u51o^mS_E-8e8DFuMS;)U%uIzHT$&H<9Dzn^Q5zJHbH+f^#+fCc!#$zh z9dK_Dg=c=&X2@1y0~50%YAJqWKCMs!7Z(L7wCTt&M>10{}G>K@Bw6{$SV{ zyE+P>E>Cg>r27LavA~6PQEFxqN@1J|Sqzy9-Eyp>0P5qol!Dg@`GL|p_~3F#LRgy+euxLg^%&`? zCAyh;W%)T}sVV9TC5c7psU^1R@ku#}dD-fAiQvPY!Fd$a!35pnkp?mv)ThiiysbzN zx}_4fYlXop5fqc)%0vM)+)$DN-Vz3_a1g6nL4)w1nQf%ytnge7Ilea@{X}EXUMt7| zZD^1WLuinX6@!0Vt2S_=+dYYtmJ4L+t8ECL<|fGUUGhXr0y0vRKMuJs1D17O!?L2DH(s=%B2 z5+R3f!5WxQAA{zPK${8CwBc6^6*m>OlLcK^qDcz<1i@=_$At zl^N}>X2g@1lpi9$N~cq>>uLzbiDrIspymXhWd zrGRQ9$O>yjVE|fX3)=k+T4S%^82~@(43yiUjouX4ZZB|C0@R%V_k|#>Uy#|bBM1>? zb#YE+F6d;p@XR9cG6Hb9Q35K#^1ye_z&kb|)6feJNGGHSdT?MOq+tyjnS-7asQ~W0 z!ww)X2cPxo5#sBk0Itn^6N|D_^23<}pu$@paA<&B2(!6vX@VF*uusJ;yRJ}kPhthwAn&p>Tnh0Ar4H~qCI!h}vPdB8p z0JO#lK4b_n6Vz7Eg^Pij=6Ud*187MT_&{c4M}w50^C7VhpW#YN1RaBft_a1s$Uy|r z4eElG7DK8eSUUt%(SWBZ!5!B;1r4NoniNtJ%kuMd6!Me6ONn4}vXE6Z(7hm<&{73j zkU)1(gN}(qgi9VM3&Fx0(vSf4e+W!NLeKt$?BWD>Yx0T{OR5lK!JzRRjbcqy0q`>U zs#HzT<_b_E15HeWdXLba4MH)lt^aTp;JOUaQU(>yuv1ImYCv;F>8T2^nxX{My#dXK zf;PM57UUO|Kr3O;s!>q)6|^n}l38+cAS>%Z>##FHO*GKRc}Zd(lK(&xpUB=QO#`1b z0=n!7Bn^I0MxBfRqe*5F238 z4j&hXBqM}@NHGh^A_yti{%R}G*e2?@Cb&EXIWn~vQWd~vLGX`)f=U?B@IScrfN24H z7Tuh1l;gk@it`m2z)KH7)3!M|kcI0E;h8Wdct-fN(J9r1#0PoT7wv?MnUxhxNI$gHPEX{6%eQNq(bh@0-YBKnx`s)tki}V z6tJVbU?)oF4lgMw zN!5dR0n~{F6;05B9~7{VQ4+|84p5B&YG{J9KBzaUfSB|FZI^;vXr%`lln0-oRfH@L z)(BdIUYcH_qfiXpQ4Q{7WkT9OvK5>c?fT)YQR=RLdr{&DL92h=*}uVkljd;4sr!Jb%ACr z!CO%v7JxDxNDg#;3F7)1R2x8J$&iabK3 zSPmi#DmX#SC&+FNNMjjXKtaYJz##!v4r=Nmx?ostoq^^d$RKG^A}E)GiXTV@1P`4T zr4^TeoCa=J6qkU?DsWK(aT>Udj_`4MQGRIweB+O*280jEJfNTiRTiM$MQTwV*if(| z@jEalKN)nq3qm=3!~+r*@N>dI$2ho@Du9gy2L@X4BouU13hZwj zdP)_NKm$gQq?!vV>p`n$Kua$3QosoeEM1%r8VEx0!4omy-K?NXev#InfF_QsN+CP$ zNmWin5F#~3Kx4W2puQD65D^I+UMwNNNM1MlR#JRWP*CPkaLJ&H9y!4xY1A^B>&_=d9Zkz^91*q8tJu(&)b$Q_X??C&=ieP6~ zf|}8!6|hOEnXrt>0Ins#cak9`VerBQSmlK3U{E3guT%i#H?Rv~r3tD#K+Sffavsb@ zE#{F+ECx{cgYso=B51(72$DIGZgPgKZ_+9vQX%+QAkd_Ka$;Uy2}3Z5O3ece3xW$z z@W^>4DBXcauR)#r!_c#MK#dt}G7Qi$8t`3_V1J{u70~ZOgeO*os#3@h6*x@cbL+6% z8qrNfZhxSg2~KX{>Xj4|QLe;7HxfO4l4LO60D$xzL8%2cy2SumSqqv|#-GqYL%@l4 z$QM0=gPbT$pne#V!o<>&jQk>`R0tJ7q(c;I{6YIc7+iHh$C-fcl}{`IjV{0k21*zb zQI4Y{+AFBqklY7O5~$e>>U+{XQdOz|URwZ~pDBi|VFf34@W^|f6$7Y+1L^=3A@61f zl_a@ZpwTeIo>W46R>2KDa5@C%0}2WvcuNlymY|Z^3F%NYaDj;wT9Bq9BDf%Yq_|8^ zg*22)zzGJ_@&eygM5#HTek^FP7j}9aTHHXKL6oyVtt(1{ED5xt64I!GdJ|L=LD~wS z>V<>=1GTjgC-!7Q238@4f_g{D4LeW*A=?h*>ISrmpronnR-zhjnff`Fl0fJuo5iC$~no6}o70KB|h6Yl>ilG@4ZSaK!csx!_h=Pmm zRM5OeMjkXr5o;BrY-1ANL|Jap|DL)6ppw|n7HGmH5B}x;>qeu#&6(+)br~p!_i);;& zPLM<3kp^)l)LpPuQ&wP?fm$3$+7LFun@WjxkoG31S&kTQgRP}f0B@O5fEh|fGr`x@ zLplizD8_^09aLE%IU6*uU0MwHFC+7bMVj%y_Kpt)|iYUKTJV8uumX+!*j zXx`%sR@i0C#OonFwv#}6Z$bO|^B5rQ3I^!9BJgeuP!l6Hj{&??2P}}1SPb2s0qZWq zj^YCEmj$;^s!AbCmPybCZLGs}f_g@f+o8dJM{sqJ+tawi4sH;r)<{ZC&(F&P^%;>O z47Xm=!w|(bl#Z_sxO&5HEo@5|D5XF$252Y69c!OW zYgf=H0}ZL?rGaj2OD$G#&dAJzZP0(dv+?EXDKK>i86Aa^qQsO;&;_-rIXRjP0F0tC AG5`Po diff --git a/locale/en_US/LC_MESSAGES/django.po b/locale/en_US/LC_MESSAGES/django.po index 1709afedd..e776c8268 100644 --- a/locale/en_US/LC_MESSAGES/django.po +++ b/locale/en_US/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-05-14 14:03+0000\n" +"POT-Creation-Date: 2022-05-23 21:04+0000\n" "PO-Revision-Date: 2021-02-28 17:19-0800\n" "Last-Translator: Mouse Reeve \n" "Language-Team: English \n" @@ -122,25 +122,25 @@ msgstr "" msgid "Automatically generated report" msgstr "" -#: bookwyrm/models/base_model.py:17 bookwyrm/models/link.py:72 +#: bookwyrm/models/base_model.py:18 bookwyrm/models/link.py:72 #: bookwyrm/templates/import/import_status.html:200 #: bookwyrm/templates/settings/link_domains/link_domains.html:19 msgid "Pending" msgstr "" -#: bookwyrm/models/base_model.py:18 +#: bookwyrm/models/base_model.py:19 msgid "Self deletion" msgstr "" -#: bookwyrm/models/base_model.py:19 +#: bookwyrm/models/base_model.py:20 msgid "Moderator suspension" msgstr "" -#: bookwyrm/models/base_model.py:20 +#: bookwyrm/models/base_model.py:21 msgid "Moderator deletion" msgstr "" -#: bookwyrm/models/base_model.py:21 +#: bookwyrm/models/base_model.py:22 msgid "Domain block" msgstr "" @@ -735,7 +735,7 @@ msgstr "" #: bookwyrm/templates/author/edit_author.html:115 #: bookwyrm/templates/book/book.html:202 -#: bookwyrm/templates/book/edit/edit_book.html:127 +#: bookwyrm/templates/book/edit/edit_book.html:135 #: bookwyrm/templates/book/file_links/add_link_modal.html:60 #: bookwyrm/templates/book/file_links/edit_links.html:82 #: bookwyrm/templates/groups/form.html:32 @@ -758,8 +758,8 @@ msgstr "" #: bookwyrm/templates/author/sync_modal.html:23 #: bookwyrm/templates/book/book.html:203 #: bookwyrm/templates/book/cover_add_modal.html:33 -#: bookwyrm/templates/book/edit/edit_book.html:129 -#: bookwyrm/templates/book/edit/edit_book.html:132 +#: bookwyrm/templates/book/edit/edit_book.html:137 +#: bookwyrm/templates/book/edit/edit_book.html:140 #: bookwyrm/templates/book/file_links/add_link_modal.html:59 #: bookwyrm/templates/book/file_links/verification_modal.html:25 #: bookwyrm/templates/book/sync_modal.html:23 @@ -781,7 +781,7 @@ msgid "Loading data will connect to %(source_name)s and check f msgstr "" #: bookwyrm/templates/author/sync_modal.html:24 -#: bookwyrm/templates/book/edit/edit_book.html:114 +#: bookwyrm/templates/book/edit/edit_book.html:122 #: bookwyrm/templates/book/sync_modal.html:24 #: bookwyrm/templates/groups/members.html:29 #: bookwyrm/templates/landing/password_reset.html:42 @@ -950,42 +950,42 @@ msgstr "" msgid "Add Book" msgstr "" -#: bookwyrm/templates/book/edit/edit_book.html:54 +#: bookwyrm/templates/book/edit/edit_book.html:62 msgid "Confirm Book Info" msgstr "" -#: bookwyrm/templates/book/edit/edit_book.html:62 +#: bookwyrm/templates/book/edit/edit_book.html:70 #, python-format msgid "Is \"%(name)s\" one of these authors?" msgstr "" -#: bookwyrm/templates/book/edit/edit_book.html:73 -#: bookwyrm/templates/book/edit/edit_book.html:75 +#: bookwyrm/templates/book/edit/edit_book.html:81 +#: bookwyrm/templates/book/edit/edit_book.html:83 msgid "Author of " msgstr "" -#: bookwyrm/templates/book/edit/edit_book.html:75 +#: bookwyrm/templates/book/edit/edit_book.html:83 msgid "Find more information at isni.org" msgstr "" -#: bookwyrm/templates/book/edit/edit_book.html:85 +#: bookwyrm/templates/book/edit/edit_book.html:93 msgid "This is a new author" msgstr "" -#: bookwyrm/templates/book/edit/edit_book.html:92 +#: bookwyrm/templates/book/edit/edit_book.html:100 #, python-format msgid "Creating a new author: %(name)s" msgstr "" -#: bookwyrm/templates/book/edit/edit_book.html:99 +#: bookwyrm/templates/book/edit/edit_book.html:107 msgid "Is this an edition of an existing work?" msgstr "" -#: bookwyrm/templates/book/edit/edit_book.html:107 +#: bookwyrm/templates/book/edit/edit_book.html:115 msgid "This is a new work" msgstr "" -#: bookwyrm/templates/book/edit/edit_book.html:116 +#: bookwyrm/templates/book/edit/edit_book.html:124 #: bookwyrm/templates/feed/status.html:21 msgid "Back" msgstr "" @@ -1971,33 +1971,33 @@ msgstr "" msgid "Data source:" msgstr "" -#: bookwyrm/templates/import/import.html:39 +#: bookwyrm/templates/import/import.html:42 msgid "You can download your Goodreads data from the Import/Export page of your Goodreads account." msgstr "" -#: bookwyrm/templates/import/import.html:44 +#: bookwyrm/templates/import/import.html:47 msgid "Data file:" msgstr "" -#: bookwyrm/templates/import/import.html:52 +#: bookwyrm/templates/import/import.html:55 msgid "Include reviews" msgstr "" -#: bookwyrm/templates/import/import.html:57 +#: bookwyrm/templates/import/import.html:60 msgid "Privacy setting for imported reviews:" msgstr "" -#: bookwyrm/templates/import/import.html:63 +#: bookwyrm/templates/import/import.html:66 #: bookwyrm/templates/preferences/layout.html:31 #: bookwyrm/templates/settings/federation/instance_blocklist.html:76 msgid "Import" msgstr "" -#: bookwyrm/templates/import/import.html:68 +#: bookwyrm/templates/import/import.html:71 msgid "Recent Imports" msgstr "" -#: bookwyrm/templates/import/import.html:70 +#: bookwyrm/templates/import/import.html:73 msgid "No recent imports" msgstr "" @@ -5114,7 +5114,7 @@ msgstr "" msgid "%(title)s: %(subtitle)s" msgstr "" -#: bookwyrm/views/imports/import_data.py:67 +#: bookwyrm/views/imports/import_data.py:70 msgid "Not a valid csv file" msgstr "" diff --git a/locale/ro_RO/LC_MESSAGES/django.mo b/locale/ro_RO/LC_MESSAGES/django.mo index 36dc9fb2c9626a636c471d26a462efc963edecd2..f64e15d56941a5d05dba5a360a979b9346f9db05 100644 GIT binary patch delta 24965 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°G7a29mK)0br5s&q4M=LP=OAp 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\n" "Language-Team: Romanian\n" "Language: ro\n" @@ -515,9 +515,9 @@ msgstr "Din păcate %(display_name)s nu a terminat nicio carte în %(year)s" #, python-format msgid "In %(year)s, %(display_name)s read %(books_total)s book
for a total of %(pages_total)s pages!" msgid_plural "In %(year)s, %(display_name)s read %(books_total)s books
for a total of %(pages_total)s pages!" -msgstr[0] "" +msgstr[0] "În %(year)s, %(display_name)s a citit %(books_total)s carte
pentru un total de %(pages_total)s pagini!" msgstr[1] "" -msgstr[2] "În %(year)s, %(display_name)s a citit %(books_total)s cărți
pentru un total de %(pages_total)s de pagini!" +msgstr[2] "În %(year)s, %(display_name)s a citit %(books_total)s cărți
pentru un total de %(pages_total)s pagini!" #: bookwyrm/templates/annual_summary/layout.html:124 msgid "That’s great!" @@ -532,7 +532,7 @@ msgstr "Asta înseamnă o medie de %(pages)s de pagini pe carte." #, python-format msgid "(%(no_page_number)s book doesn’t have pages)" msgid_plural "(%(no_page_number)s books don’t have pages)" -msgstr[0] "" +msgstr[0] "(cartea %(no_page_number)s nu are pagini)" msgstr[1] "" msgstr[2] "(cărțile %(no_page_number)s nu au pagini)" @@ -576,9 +576,9 @@ msgstr "Felicitări!" #, python-format msgid "%(display_name)s left %(ratings_total)s rating,
their average rating is %(rating_average)s" msgid_plural "%(display_name)s left %(ratings_total)s ratings,
their average rating is %(rating_average)s" -msgstr[0] "" +msgstr[0] "%(display_name)s a lăsat %(ratings_total)s recenzie,
ratingul său mediu este %(rating_average)s" msgstr[1] "" -msgstr[2] "%(display_name)s a lăsat recenzii de %(ratings_total)s,
ratingul său mediu este %(rating_average)s" +msgstr[2] "%(display_name)s a lăsat %(ratings_total)s recenzii,
ratingul său mediu este %(rating_average)s" #: bookwyrm/templates/annual_summary/layout.html:238 msgid "Their best rated review" @@ -816,7 +816,7 @@ msgstr "Clic pentru a mări" #, python-format msgid "(%(review_count)s review)" msgid_plural "(%(review_count)s reviews)" -msgstr[0] "" +msgstr[0] "(%(review_count)s recenzie)" msgstr[1] "" msgstr[2] "(%(review_count)s recenzii)" @@ -834,7 +834,7 @@ msgstr "Descriere:" #, python-format msgid "%(count)s edition" msgid_plural "%(count)s editions" -msgstr[0] "" +msgstr[0] "%(count)s ediție" msgstr[1] "" msgstr[2] "%(count)s ediții" @@ -3379,7 +3379,7 @@ msgstr "Opere" #, python-format msgid "%(display_count)s open report" msgid_plural "%(display_count)s open reports" -msgstr[0] "" +msgstr[0] "%(display_count)s raport deschis" msgstr[1] "" msgstr[2] "%(display_count)s raporturi dechise" From 0f7317f8fee89544e9782031a0d18edf1943c7b5 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 23 May 2022 15:31:05 -0700 Subject: [PATCH 36/85] Make an exception for yourself when followers are hidden --- bookwyrm/views/user.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/views/user.py b/bookwyrm/views/user.py index eaddf5852..e00aaa8e4 100644 --- a/bookwyrm/views/user.py +++ b/bookwyrm/views/user.py @@ -106,7 +106,7 @@ class Followers(View): if is_api_request(request): return ActivitypubResponse(user.to_followers_activity(**request.GET)) - if user.hide_follows: + if user.hide_follows and user != request.user: raise PermissionDenied() followers = annotate_if_follows(request.user, user.followers) @@ -129,7 +129,7 @@ class Following(View): if is_api_request(request): return ActivitypubResponse(user.to_following_activity(**request.GET)) - if user.hide_follows: + if user.hide_follows and user != request.user: raise PermissionDenied() following = annotate_if_follows(request.user, user.following) From 0bfe1e9dfc0934b5c6d929cc056f86e70cced74b Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 25 May 2022 13:24:11 -0700 Subject: [PATCH 37/85] Don't throw an error when unable to connect to remote data --- bookwyrm/activitypub/base_activity.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 448d55637..58c59e5b0 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -1,6 +1,7 @@ """ basics for an activitypub serializer """ from dataclasses import dataclass, fields, MISSING from json import JSONEncoder +import logging from django.apps import apps from django.db import IntegrityError, transaction @@ -8,6 +9,8 @@ from django.db import IntegrityError, transaction from bookwyrm.connectors import ConnectorException, get_data from bookwyrm.tasks import app +logger = logging.getLogger(__name__) + class ActivitySerializerError(ValueError): """routine problems serializing activitypub json""" @@ -268,9 +271,11 @@ def resolve_remote_id( try: data = get_data(remote_id) except ConnectorException: - raise ActivitySerializerError( - f"Could not connect to host for remote_id: {remote_id}" + logger.exception( + "Could not connect to host for remote_id: %s", remote_id ) + return None + # determine the model implicitly, if not provided # or if it's a model with subclasses like Status, check again if not model or hasattr(model.objects, "select_subclasses"): From 3e54a5f4a3f974133b1020c34504b1fb62cc9a5d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 26 May 2022 09:00:45 -0700 Subject: [PATCH 38/85] Python formatting --- bookwyrm/activitypub/base_activity.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 58c59e5b0..63ec2d016 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -271,9 +271,7 @@ def resolve_remote_id( try: data = get_data(remote_id) except ConnectorException: - logger.exception( - "Could not connect to host for remote_id: %s", remote_id - ) + logger.exception("Could not connect to host for remote_id: %s", remote_id) return None # determine the model implicitly, if not provided From 9d275db3227145271696fff26add7c6f4c792b88 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 26 May 2022 09:57:39 -0700 Subject: [PATCH 39/85] Updates ignore boost logic that no longer produces errors --- bookwyrm/models/status.py | 7 ++----- bookwyrm/tests/models/test_status_model.py | 2 ++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 17fcd4587..45e2cbae3 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -116,11 +116,8 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): def ignore_activity(cls, activity): # pylint: disable=too-many-return-statements """keep notes if they are replies to existing statuses""" if activity.type == "Announce": - try: - boosted = activitypub.resolve_remote_id( - activity.object, get_activity=True - ) - except activitypub.ActivitySerializerError: + boosted = activitypub.resolve_remote_id(activity.object, get_activity=True) + if not boosted: # if we can't load the status, definitely ignore it return True # keep the boost if we would keep the status diff --git a/bookwyrm/tests/models/test_status_model.py b/bookwyrm/tests/models/test_status_model.py index 837cd41df..2f1e8670f 100644 --- a/bookwyrm/tests/models/test_status_model.py +++ b/bookwyrm/tests/models/test_status_model.py @@ -462,6 +462,8 @@ class Status(TestCase): @responses.activate def test_ignore_activity_boost(self, *_): """don't bother with most remote statuses""" + responses.add(responses.GET, "http://fish.com/nothing") + activity = activitypub.Announce( id="http://www.faraway.com/boost/12", actor=self.remote_user.remote_id, From 23c60193400da3ea35b1cca5d822419cc5a37ea8 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 26 May 2022 10:23:32 -0700 Subject: [PATCH 40/85] Adds merge migration --- bookwyrm/migrations/0149_merge_20220526_1716.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 bookwyrm/migrations/0149_merge_20220526_1716.py diff --git a/bookwyrm/migrations/0149_merge_20220526_1716.py b/bookwyrm/migrations/0149_merge_20220526_1716.py new file mode 100644 index 000000000..b42bccd3b --- /dev/null +++ b/bookwyrm/migrations/0149_merge_20220526_1716.py @@ -0,0 +1,13 @@ +# Generated by Django 3.2.13 on 2022-05-26 17:16 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0148_alter_user_preferred_language"), + ("bookwyrm", "0148_merge_20220326_2006"), + ] + + operations = [] From 007751c8cb92e0645bb3a72666d90f03c1186369 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 26 May 2022 10:58:11 -0700 Subject: [PATCH 41/85] Adds error logging to status views --- bookwyrm/views/reading.py | 5 +++++ bookwyrm/views/status.py | 10 ++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/bookwyrm/views/reading.py b/bookwyrm/views/reading.py index 3cd153d2f..2aa9136d0 100644 --- a/bookwyrm/views/reading.py +++ b/bookwyrm/views/reading.py @@ -1,4 +1,5 @@ """ the good stuff! the books! """ +import logging from django.contrib.auth.decorators import login_required from django.core.cache import cache from django.db import transaction @@ -15,6 +16,8 @@ from .status import CreateStatus from .helpers import get_edition, handle_reading_status, is_api_request from .helpers import load_date_in_user_tz_as_utc +logger = logging.getLogger(__name__) + # pylint: disable=no-self-use # pylint: disable=too-many-return-statements @@ -36,6 +39,7 @@ class ReadingStatus(View): # redirect if we're already on this shelf return TemplateResponse(request, f"reading_progress/{template}", {"book": book}) + @transaction.atomic def post(self, request, status, book_id): """Change the state of a book by shelving it and adding reading dates""" identifier = { @@ -45,6 +49,7 @@ class ReadingStatus(View): "stop": models.Shelf.STOPPED_READING, }.get(status) if not identifier: + logger.exception("Invalid reading status type: %s", status) return HttpResponseBadRequest() # invalidate related caches diff --git a/bookwyrm/views/status.py b/bookwyrm/views/status.py index af15064fd..670ea5717 100644 --- a/bookwyrm/views/status.py +++ b/bookwyrm/views/status.py @@ -1,5 +1,6 @@ """ what are we here for if not for posting """ import re +import logging from urllib.parse import urlparse from django.contrib.auth.decorators import login_required @@ -21,6 +22,8 @@ from bookwyrm.utils import regex from .helpers import handle_remote_webfinger, is_api_request from .helpers import load_date_in_user_tz_as_utc +logger = logging.getLogger(__name__) + # pylint: disable= no-self-use @method_decorator(login_required, name="dispatch") @@ -72,11 +75,14 @@ class CreateStatus(View): form = getattr(forms, f"{status_type}Form")( request.POST, instance=existing_status ) - except AttributeError: + except AttributeError as err: + logger.exception(err) return HttpResponseBadRequest() + if not form.is_valid(): if is_api_request(request): - return HttpResponse(status=500) + logger.exception(form.errors) + return HttpResponseBadRequest() return redirect(request.headers.get("Referer", "/")) status = form.save(commit=False) From 6848616ff14421e7399425e5cbf6bc064c370bfc Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 26 May 2022 11:09:11 -0700 Subject: [PATCH 42/85] Fixes reading status field in stop modal The value of the reading status needs to match one of the database options for `reading_status` in the `Comment` model --- .../templates/snippets/reading_modals/stop_reading_modal.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/templates/snippets/reading_modals/stop_reading_modal.html b/bookwyrm/templates/snippets/reading_modals/stop_reading_modal.html index 80fb2d5b3..571a9ec59 100644 --- a/bookwyrm/templates/snippets/reading_modals/stop_reading_modal.html +++ b/bookwyrm/templates/snippets/reading_modals/stop_reading_modal.html @@ -12,7 +12,7 @@ Stop Reading "{{ book_title }}" {% csrf_token %} - + {% endblock %} From 92dbfec5f8d11cc93c1a5460238b411a23af64fd Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 26 May 2022 11:10:14 -0700 Subject: [PATCH 43/85] Adds status header for stopped reading statuses --- .../status/headers/stopped_reading.html | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 bookwyrm/templates/snippets/status/headers/stopped_reading.html diff --git a/bookwyrm/templates/snippets/status/headers/stopped_reading.html b/bookwyrm/templates/snippets/status/headers/stopped_reading.html new file mode 100644 index 000000000..3b6a314e1 --- /dev/null +++ b/bookwyrm/templates/snippets/status/headers/stopped_reading.html @@ -0,0 +1,23 @@ +{% spaceless %} +{% load i18n %} +{% load utilities %} +{% load status_display %} + +{% load_book status as book %} +{% if book.authors.exists %} + +{% with author=book.authors.first %} +{% blocktrans trimmed with book_path=book.local_path book=book|book_title author_name=author.name author_path=author.local_path %} +stopped reading {{ book }} by {{ author_name }} +{% endblocktrans %} +{% endwith %} + +{% else %} + +{% blocktrans trimmed with book_path=book.local_path book=book|book_title %} +stopped reading {{ book }} +{% endblocktrans %} + +{% endif %} +{% endspaceless %} + From 9b4a49866114cef37802a82f48a6db4e1d38e14d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 26 May 2022 11:19:49 -0700 Subject: [PATCH 44/85] Don't show a button for the shelf a book is currently on This will lead to nonsensical modal states --- .../snippets/shelve_button/shelve_button_dropdown_options.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bookwyrm/templates/snippets/shelve_button/shelve_button_dropdown_options.html b/bookwyrm/templates/snippets/shelve_button/shelve_button_dropdown_options.html index 50d3e2574..bc5ff24ad 100644 --- a/bookwyrm/templates/snippets/shelve_button/shelve_button_dropdown_options.html +++ b/bookwyrm/templates/snippets/shelve_button/shelve_button_dropdown_options.html @@ -6,6 +6,7 @@ {% for shelf in shelves %} {% comparison_bool shelf.identifier active_shelf.shelf.identifier as is_current %} + {% if not is_current %} + {% endif %} {% endfor %} {% if readthrough and active_shelf.shelf.identifier != 'read' %} From 1f6fbd8d29c297f266179e06f8ad8831902a6649 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 26 May 2022 11:28:54 -0700 Subject: [PATCH 45/85] Fixes stopped reading button logic The stopped state is similar to finished --- .../shelve_button/shelve_button_options.html | 16 +++++++++------- bookwyrm/templatetags/shelf_tags.py | 2 ++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/bookwyrm/templates/snippets/shelve_button/shelve_button_options.html b/bookwyrm/templates/snippets/shelve_button/shelve_button_options.html index fb7ee452c..891f58630 100644 --- a/bookwyrm/templates/snippets/shelve_button/shelve_button_options.html +++ b/bookwyrm/templates/snippets/shelve_button/shelve_button_options.html @@ -13,6 +13,15 @@ +
+ +
+ {% for shelf in shelves %}
Date: Thu, 26 May 2022 11:33:31 -0700 Subject: [PATCH 46/85] Adds stopped date separate from finish date on readthrough --- bookwyrm/forms/forms.py | 7 ++++++- .../0150_readthrough_stopped_date.py | 18 ++++++++++++++++++ bookwyrm/models/readthrough.py | 1 + .../readthrough/readthrough_list.html | 2 ++ .../reading_modals/stop_reading_modal.html | 4 ++-- 5 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 bookwyrm/migrations/0150_readthrough_stopped_date.py diff --git a/bookwyrm/forms/forms.py b/bookwyrm/forms/forms.py index 9d8f9f392..4aa1e5758 100644 --- a/bookwyrm/forms/forms.py +++ b/bookwyrm/forms/forms.py @@ -53,7 +53,12 @@ class ReadThroughForm(CustomForm): self.add_error( "finish_date", _("Reading finish date cannot be before start date.") ) + stopped_date = cleaned_data.get("stopped_date") + if start_date and stopped_date and start_date > stopped_date: + self.add_error( + "stopped_date", _("Reading stopped date cannot be before start date.") + ) class Meta: model = models.ReadThrough - fields = ["user", "book", "start_date", "finish_date"] + fields = ["user", "book", "start_date", "finish_date", "stopped_date"] diff --git a/bookwyrm/migrations/0150_readthrough_stopped_date.py b/bookwyrm/migrations/0150_readthrough_stopped_date.py new file mode 100644 index 000000000..6ce2f89a9 --- /dev/null +++ b/bookwyrm/migrations/0150_readthrough_stopped_date.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.13 on 2022-05-26 18:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0149_merge_20220526_1716"), + ] + + operations = [ + migrations.AddField( + model_name="readthrough", + name="stopped_date", + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/bookwyrm/models/readthrough.py b/bookwyrm/models/readthrough.py index ceb8e0b6e..5a13fa473 100644 --- a/bookwyrm/models/readthrough.py +++ b/bookwyrm/models/readthrough.py @@ -27,6 +27,7 @@ class ReadThrough(BookWyrmModel): ) start_date = models.DateTimeField(blank=True, null=True) finish_date = models.DateTimeField(blank=True, null=True) + stopped_date = models.DateTimeField(blank=True, null=True) is_active = models.BooleanField(default=True) def save(self, *args, **kwargs): diff --git a/bookwyrm/templates/readthrough/readthrough_list.html b/bookwyrm/templates/readthrough/readthrough_list.html index d0d679da7..7a89947d6 100644 --- a/bookwyrm/templates/readthrough/readthrough_list.html +++ b/bookwyrm/templates/readthrough/readthrough_list.html @@ -12,6 +12,8 @@
  • {% if readthrough.finish_date %} {{ readthrough.finish_date | localtime | naturalday }}: {% trans "finished" %} + {% elif readthrough.stopped_date %} + {{ readthrough.stopped_date | localtime | naturalday }}: {% trans "stopped" %} {% else %} {% if readthrough.progress_mode == 'PG' %} diff --git a/bookwyrm/templates/snippets/reading_modals/stop_reading_modal.html b/bookwyrm/templates/snippets/reading_modals/stop_reading_modal.html index 571a9ec59..8e5ae7d65 100644 --- a/bookwyrm/templates/snippets/reading_modals/stop_reading_modal.html +++ b/bookwyrm/templates/snippets/reading_modals/stop_reading_modal.html @@ -29,9 +29,9 @@ Stop Reading "{{ book_title }}"
    - +
  • From dfe0656eb4d9e85d2d9140111928d0e4f3889525 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 26 May 2022 11:38:36 -0700 Subject: [PATCH 47/85] Typo fix --- bookwyrm/templatetags/shelf_tags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/templatetags/shelf_tags.py b/bookwyrm/templatetags/shelf_tags.py index b4f53e482..57cd02259 100644 --- a/bookwyrm/templatetags/shelf_tags.py +++ b/bookwyrm/templatetags/shelf_tags.py @@ -31,7 +31,7 @@ def get_next_shelf(current_shelf): if current_shelf == "read": return "complete" if current_shelf == "stopped-reading": - return "stopped-readingt" + return "stopped-reading" return "to-read" From 4c5d2570abc9bbf0b503174e792bc04e7a96cb81 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 26 May 2022 11:53:33 -0700 Subject: [PATCH 48/85] Save and display stopped date in readthrough --- bookwyrm/models/readthrough.py | 2 +- bookwyrm/templates/readthrough/readthrough_form.html | 1 + bookwyrm/templates/readthrough/readthrough_list.html | 2 +- bookwyrm/views/reading.py | 7 +++++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/bookwyrm/models/readthrough.py b/bookwyrm/models/readthrough.py index 5a13fa473..314b40a5c 100644 --- a/bookwyrm/models/readthrough.py +++ b/bookwyrm/models/readthrough.py @@ -35,7 +35,7 @@ class ReadThrough(BookWyrmModel): cache.delete(f"latest_read_through-{self.user.id}-{self.book.id}") self.user.update_active_date() # an active readthrough must have an unset finish date - if self.finish_date: + if self.finish_date or self.stopped_date: self.is_active = False super().save(*args, **kwargs) diff --git a/bookwyrm/templates/readthrough/readthrough_form.html b/bookwyrm/templates/readthrough/readthrough_form.html index 1558dada4..45c92043a 100644 --- a/bookwyrm/templates/readthrough/readthrough_form.html +++ b/bookwyrm/templates/readthrough/readthrough_form.html @@ -19,6 +19,7 @@ {% include "snippets/progress_field.html" with id=field_id %} {% endif %} +