diff --git a/.github/workflows/django-tests.yml b/.github/workflows/django-tests.yml
index 03147744..03875193 100644
--- a/.github/workflows/django-tests.yml
+++ b/.github/workflows/django-tests.yml
@@ -36,6 +36,7 @@ jobs:
env:
SECRET_KEY: beepbeep
DEBUG: false
+ USE_HTTPS: true
DOMAIN: your.domain.here
BOOKWYRM_DATABASE_BACKEND: postgres
MEDIA_ROOT: images/
diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py
index da32fbaf..ddd45426 100644
--- a/bookwyrm/activitypub/base_activity.py
+++ b/bookwyrm/activitypub/base_activity.py
@@ -48,7 +48,7 @@ class Signature:
def naive_parse(activity_objects, activity_json, serializer=None):
- """this navigates circular import issues"""
+ """this navigates circular import issues by looking up models' serializers"""
if not serializer:
if activity_json.get("publicKeyPem"):
# ugh
diff --git a/bookwyrm/activitystreams.py b/bookwyrm/activitystreams.py
index ebe2cbd9..0755314b 100644
--- a/bookwyrm/activitystreams.py
+++ b/bookwyrm/activitystreams.py
@@ -6,7 +6,6 @@ from django.db.models import signals, Q
from bookwyrm import models
from bookwyrm.redis_store import RedisStore, r
from bookwyrm.tasks import app
-from bookwyrm.settings import STREAMS
from bookwyrm.views.helpers import privacy_filter
@@ -58,7 +57,13 @@ class ActivityStream(RedisStore):
return (
models.Status.objects.select_subclasses()
.filter(id__in=statuses)
- .select_related("user", "reply_parent")
+ .select_related(
+ "user",
+ "reply_parent",
+ "comment__book",
+ "review__book",
+ "quotation__book",
+ )
.prefetch_related("mention_books", "mention_users")
.order_by("-published_date")
)
@@ -237,15 +242,10 @@ class BooksStream(ActivityStream):
# determine which streams are enabled in settings.py
-available_streams = [s["key"] for s in STREAMS]
streams = {
- k: v
- for (k, v) in {
- "home": HomeStream(),
- "local": LocalStream(),
- "books": BooksStream(),
- }.items()
- if k in available_streams
+ "home": HomeStream(),
+ "local": LocalStream(),
+ "books": BooksStream(),
}
@@ -261,8 +261,6 @@ def add_status_on_create(sender, instance, created, *args, **kwargs):
remove_status_task.delay(instance.id)
return
- if not created:
- return
# when creating new things, gotta wait on the transaction
transaction.on_commit(lambda: add_status_on_create_command(sender, instance))
@@ -395,8 +393,53 @@ def remove_statuses_on_shelve(sender, instance, *args, **kwargs):
BooksStream().remove_book_statuses(instance.user, instance.book)
+@receiver(signals.pre_save, sender=models.ShelfBook)
+# pylint: disable=unused-argument
+def add_statuses_on_shelve(sender, instance, *args, **kwargs):
+ """update books stream when user shelves a book"""
+ if not instance.user.local:
+ return
+ book = None
+ if hasattr(instance, "book"):
+ book = instance.book
+ elif instance.mention_books.exists():
+ book = instance.mention_books.first()
+ if not book:
+ return
+
+ # check if the book is already on the user's shelves
+ editions = book.parent_work.editions.all()
+ if models.ShelfBook.objects.filter(user=instance.user, book__in=editions).exists():
+ return
+
+ BooksStream().add_book_statuses(instance.user, book)
+
+
+@receiver(signals.post_delete, sender=models.ShelfBook)
+# pylint: disable=unused-argument
+def remove_statuses_on_unshelve(sender, instance, *args, **kwargs):
+ """update books stream when user unshelves a book"""
+ if not instance.user.local:
+ return
+
+ book = None
+ if hasattr(instance, "book"):
+ book = instance.book
+ elif instance.mention_books.exists():
+ book = instance.mention_books.first()
+ if not book:
+ return
+ # check if the book is actually unshelved, not just moved
+ editions = book.parent_work.editions.all()
+ if models.ShelfBook.objects.filter(user=instance.user, book__in=editions).exists():
+ return
+
+ BooksStream().remove_book_statuses(instance.user, instance.book)
+
+
# ---- TASKS
+# TODO: merge conflict: reconcile these tasks
@app.task
def populate_streams_task(user_id):
@@ -406,6 +449,14 @@ def populate_streams_task(user_id):
stream.populate_streams(user)
+@app.task
+def populate_stream_task(stream, user_id):
+ """background task for populating an empty activitystream"""
+ user = models.User.objects.get(id=user_id)
+ stream = streams[stream]
+ stream.populate_streams(user)
+
+
@app.task
def remove_status_task(status_ids):
"""remove a status from any stream it might be in"""
@@ -444,4 +495,4 @@ def add_user_statuses_task(viewer_id, user_id, stream_list=None):
viewer = models.User.objects.get(id=viewer_id)
user = models.User.objects.get(id=user_id)
for stream in stream_list:
- stream.add_user_statuses(viewer, user)
+ stream.add_user_statuses(viewer, user)
\ No newline at end of file
diff --git a/bookwyrm/connectors/inventaire.py b/bookwyrm/connectors/inventaire.py
index 116aa5c1..842d0997 100644
--- a/bookwyrm/connectors/inventaire.py
+++ b/bookwyrm/connectors/inventaire.py
@@ -71,7 +71,7 @@ class Connector(AbstractConnector):
# flatten the data so that images, uri, and claims are on the same level
return {
**data.get("claims", {}),
- **{k: data.get(k) for k in ["uri", "image", "labels", "sitelinks"]},
+ **{k: data.get(k) for k in ["uri", "image", "labels", "sitelinks", "type"]},
}
def search(self, query, min_confidence=None): # pylint: disable=arguments-differ
diff --git a/bookwyrm/emailing.py b/bookwyrm/emailing.py
index 657310b0..fff3985e 100644
--- a/bookwyrm/emailing.py
+++ b/bookwyrm/emailing.py
@@ -23,6 +23,14 @@ def email_data():
}
+def email_confirmation_email(user):
+ """newly registered users confirm email address"""
+ data = email_data()
+ data["confirmation_code"] = user.confirmation_code
+ data["confirmation_link"] = user.confirmation_link
+ send_email.delay(user.email, *format_email("confirm", data))
+
+
def invite_email(invite_request):
"""send out an invite code"""
data = email_data()
diff --git a/bookwyrm/management/commands/populate_streams.py b/bookwyrm/management/commands/populate_streams.py
index f8aa21a5..a04d7f5a 100644
--- a/bookwyrm/management/commands/populate_streams.py
+++ b/bookwyrm/management/commands/populate_streams.py
@@ -3,22 +3,35 @@ from django.core.management.base import BaseCommand
from bookwyrm import activitystreams, models
-def populate_streams():
+def populate_streams(stream=None):
"""build all the streams for all the users"""
+ streams = [stream] if stream else activitystreams.streams.keys()
+ print("Populations streams", streams)
users = models.User.objects.filter(
local=True,
is_active=True,
- )
+ ).order_by("-last_active_date")
+ print("This may take a long time! Please be patient.")
for user in users:
- for stream in activitystreams.streams.values():
- stream.populate_streams(user)
+ for stream_key in streams:
+ print(".", end="")
+ activitystreams.populate_stream_task.delay(stream_key, user.id)
class Command(BaseCommand):
"""start all over with user streams"""
help = "Populate streams for all users"
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ "--stream",
+ default=None,
+ help="Specifies which time of stream to populate",
+ )
+
# pylint: disable=no-self-use,unused-argument
def handle(self, *args, **options):
"""run feed builder"""
- populate_streams()
+ stream = options.get("stream")
+ populate_streams(stream=stream)
diff --git a/bookwyrm/migrations/0081_alter_user_last_active_date.py b/bookwyrm/migrations/0081_alter_user_last_active_date.py
new file mode 100644
index 00000000..dc6b640f
--- /dev/null
+++ b/bookwyrm/migrations/0081_alter_user_last_active_date.py
@@ -0,0 +1,19 @@
+# Generated by Django 3.2.4 on 2021-08-06 02:51
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("bookwyrm", "0080_alter_shelfbook_options"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="user",
+ name="last_active_date",
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ ]
diff --git a/bookwyrm/migrations/0082_auto_20210806_2324.py b/bookwyrm/migrations/0082_auto_20210806_2324.py
new file mode 100644
index 00000000..ab0aa158
--- /dev/null
+++ b/bookwyrm/migrations/0082_auto_20210806_2324.py
@@ -0,0 +1,56 @@
+# Generated by Django 3.2.4 on 2021-08-06 23:24
+
+import bookwyrm.models.base_model
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("bookwyrm", "0081_alter_user_last_active_date"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="sitesettings",
+ name="require_confirm_email",
+ field=models.BooleanField(default=True),
+ ),
+ migrations.AddField(
+ model_name="user",
+ name="confirmation_code",
+ field=models.CharField(
+ default=bookwyrm.models.base_model.new_access_code, max_length=32
+ ),
+ ),
+ migrations.AlterField(
+ model_name="connector",
+ name="deactivation_reason",
+ field=models.CharField(
+ blank=True,
+ choices=[
+ ("pending", "Pending"),
+ ("self_deletion", "Self Deletion"),
+ ("moderator_deletion", "Moderator Deletion"),
+ ("domain_block", "Domain Block"),
+ ],
+ max_length=255,
+ null=True,
+ ),
+ ),
+ migrations.AlterField(
+ model_name="user",
+ name="deactivation_reason",
+ field=models.CharField(
+ blank=True,
+ choices=[
+ ("pending", "Pending"),
+ ("self_deletion", "Self Deletion"),
+ ("moderator_deletion", "Moderator Deletion"),
+ ("domain_block", "Domain Block"),
+ ],
+ max_length=255,
+ null=True,
+ ),
+ ),
+ ]
diff --git a/bookwyrm/models/activitypub_mixin.py b/bookwyrm/models/activitypub_mixin.py
index 729d9cba..4e313723 100644
--- a/bookwyrm/models/activitypub_mixin.py
+++ b/bookwyrm/models/activitypub_mixin.py
@@ -7,7 +7,7 @@ import operator
import logging
from uuid import uuid4
import requests
-from requests.exceptions import HTTPError, SSLError
+from requests.exceptions import RequestException
from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15
@@ -43,7 +43,7 @@ class ActivitypubMixin:
reverse_unfurl = False
def __init__(self, *args, **kwargs):
- """collect some info on model fields"""
+ """collect some info on model fields for later use"""
self.image_fields = []
self.many_to_many_fields = []
self.simple_fields = [] # "simple"
@@ -503,7 +503,7 @@ def broadcast_task(sender_id, activity, recipients):
for recipient in recipients:
try:
sign_and_send(sender, activity, recipient)
- except (HTTPError, SSLError, requests.exceptions.ConnectionError):
+ except RequestException:
pass
diff --git a/bookwyrm/models/base_model.py b/bookwyrm/models/base_model.py
index 2cb7c036..5b55ea50 100644
--- a/bookwyrm/models/base_model.py
+++ b/bookwyrm/models/base_model.py
@@ -1,4 +1,6 @@
""" base model with default fields """
+import base64
+from Crypto import Random
from django.db import models
from django.dispatch import receiver
@@ -9,6 +11,7 @@ from .fields import RemoteIdField
DeactivationReason = models.TextChoices(
"DeactivationReason",
[
+ "pending",
"self_deletion",
"moderator_deletion",
"domain_block",
@@ -16,6 +19,11 @@ DeactivationReason = models.TextChoices(
)
+def new_access_code():
+ """the identifier for a user invite"""
+ return base64.b32encode(Random.get_random_bytes(5)).decode("ascii")
+
+
class BookWyrmModel(models.Model):
"""shared fields"""
diff --git a/bookwyrm/models/import_job.py b/bookwyrm/models/import_job.py
index f2993846..05aada16 100644
--- a/bookwyrm/models/import_job.py
+++ b/bookwyrm/models/import_job.py
@@ -80,7 +80,7 @@ class ImportItem(models.Model):
else:
# don't fall back on title/author search is isbn is present.
# you're too likely to mismatch
- self.get_book_from_title_author()
+ self.book = self.get_book_from_title_author()
def get_book_from_isbn(self):
"""search by isbn"""
diff --git a/bookwyrm/models/site.py b/bookwyrm/models/site.py
index 872f6b45..ef3f7c3c 100644
--- a/bookwyrm/models/site.py
+++ b/bookwyrm/models/site.py
@@ -1,8 +1,6 @@
""" the particulars for this instance of BookWyrm """
-import base64
import datetime
-from Crypto import Random
from django.db import models, IntegrityError
from django.dispatch import receiver
from django.utils import timezone
@@ -10,7 +8,7 @@ from model_utils import FieldTracker
from bookwyrm.preview_images import generate_site_preview_image_task
from bookwyrm.settings import DOMAIN, ENABLE_PREVIEW_IMAGES
-from .base_model import BookWyrmModel
+from .base_model import BookWyrmModel, new_access_code
from .user import User
@@ -33,6 +31,7 @@ class SiteSettings(models.Model):
# registration
allow_registration = models.BooleanField(default=True)
allow_invite_requests = models.BooleanField(default=True)
+ require_confirm_email = models.BooleanField(default=True)
# images
logo = models.ImageField(upload_to="logos/", null=True, blank=True)
@@ -61,11 +60,6 @@ class SiteSettings(models.Model):
return default_settings
-def new_access_code():
- """the identifier for a user invite"""
- return base64.b32encode(Random.get_random_bytes(5)).decode("ascii")
-
-
class SiteInvite(models.Model):
"""gives someone access to create an account on the instance"""
diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py
index 21b6bbaa..e10bcd29 100644
--- a/bookwyrm/models/user.py
+++ b/bookwyrm/models/user.py
@@ -17,16 +17,22 @@ from bookwyrm.connectors import get_data, ConnectorException
from bookwyrm.models.shelf import Shelf
from bookwyrm.models.status import Status, Review
from bookwyrm.preview_images import generate_user_preview_image_task
-from bookwyrm.settings import DOMAIN, ENABLE_PREVIEW_IMAGES
+from bookwyrm.settings import DOMAIN, ENABLE_PREVIEW_IMAGES, USE_HTTPS
from bookwyrm.signatures import create_key_pair
from bookwyrm.tasks import app
from bookwyrm.utils import regex
from .activitypub_mixin import OrderedCollectionPageMixin, ActivitypubMixin
-from .base_model import BookWyrmModel, DeactivationReason
+from .base_model import BookWyrmModel, DeactivationReason, new_access_code
from .federated_server import FederatedServer
from . import fields, Review
+def site_link():
+ """helper for generating links to the site"""
+ protocol = "https" if USE_HTTPS else "http"
+ return f"{protocol}://{DOMAIN}"
+
+
class User(OrderedCollectionPageMixin, AbstractUser):
"""a user who wants to read books"""
@@ -111,7 +117,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
remote_id = fields.RemoteIdField(null=True, unique=True, activitypub_field="id")
created_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now=True)
- last_active_date = models.DateTimeField(auto_now=True)
+ last_active_date = models.DateTimeField(default=timezone.now)
manually_approves_followers = fields.BooleanField(default=False)
show_goal = models.BooleanField(default=True)
discoverable = fields.BooleanField(default=False)
@@ -123,11 +129,18 @@ class User(OrderedCollectionPageMixin, AbstractUser):
deactivation_reason = models.CharField(
max_length=255, choices=DeactivationReason.choices, null=True, blank=True
)
+ confirmation_code = models.CharField(max_length=32, default=new_access_code)
name_field = "username"
property_fields = [("following_link", "following")]
field_tracker = FieldTracker(fields=["name", "avatar"])
+ @property
+ def confirmation_link(self):
+ """helper for generating confirmation links"""
+ link = site_link()
+ return f"{link}/confirm-email/{self.confirmation_code}"
+
@property
def following_link(self):
"""just how to find out the following info"""
@@ -207,7 +220,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
self.following.order_by("-updated_date").all(),
remote_id=remote_id,
id_only=True,
- **kwargs
+ **kwargs,
)
def to_followers_activity(self, **kwargs):
@@ -217,7 +230,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
self.followers.order_by("-updated_date").all(),
remote_id=remote_id,
id_only=True,
- **kwargs
+ **kwargs,
)
def to_activity(self, **kwargs):
@@ -259,9 +272,9 @@ class User(OrderedCollectionPageMixin, AbstractUser):
return
# populate fields for local users
- self.remote_id = "https://%s/user/%s" % (DOMAIN, self.localname)
+ self.remote_id = "%s/user/%s" % (site_link(), self.localname)
self.inbox = "%s/inbox" % self.remote_id
- self.shared_inbox = "https://%s/inbox" % DOMAIN
+ self.shared_inbox = "%s/inbox" % site_link()
self.outbox = "%s/outbox" % self.remote_id
# an id needs to be set before we can proceed with related models
diff --git a/bookwyrm/static/css/bookwyrm.css b/bookwyrm/static/css/bookwyrm.css
index d10fb9b7..8fbdcfc6 100644
--- a/bookwyrm/static/css/bookwyrm.css
+++ b/bookwyrm/static/css/bookwyrm.css
@@ -29,6 +29,11 @@ body {
min-width: 75% !important;
}
+.clip-text {
+ max-height: 35em;
+ overflow: hidden;
+}
+
/** Utilities not covered by Bulma
******************************************************************************/
diff --git a/bookwyrm/static/js/bookwyrm.js b/bookwyrm/static/js/bookwyrm.js
index e43ed134..a4002c2d 100644
--- a/bookwyrm/static/js/bookwyrm.js
+++ b/bookwyrm/static/js/bookwyrm.js
@@ -164,7 +164,7 @@ let BookWyrm = new class {
}
// Show/hide container.
- let container = document.getElementById('hide-' + targetId);
+ let container = document.getElementById('hide_' + targetId);
if (container) {
this.toggleContainer(container, pressed);
@@ -219,7 +219,7 @@ let BookWyrm = new class {
/**
* Check or uncheck a checbox.
*
- * @param {object} checkbox - DOM node
+ * @param {string} checkbox - id of the checkbox
* @param {boolean} pressed - Is the trigger pressed?
* @return {undefined}
*/
diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py
index 3a95ef7f..9c42d79d 100644
--- a/bookwyrm/suggested_users.py
+++ b/bookwyrm/suggested_users.py
@@ -19,7 +19,7 @@ class SuggestedUsers(RedisStore):
def get_rank(self, obj):
"""get computed rank"""
- return obj.mutuals + (1.0 - (1.0 / (obj.shared_books + 1)))
+ return obj.mutuals # + (1.0 - (1.0 / (obj.shared_books + 1)))
def store_id(self, user): # pylint: disable=no-self-use
"""the key used to store this user's recs"""
@@ -31,7 +31,7 @@ class SuggestedUsers(RedisStore):
"""calculate mutuals count and shared books count from rank"""
return {
"mutuals": math.floor(rank),
- "shared_books": int(1 / (-1 * (rank % 1 - 1))) - 1,
+ # "shared_books": int(1 / (-1 * (rank % 1 - 1))) - 1,
}
def get_objects_for_store(self, store):
@@ -95,7 +95,7 @@ class SuggestedUsers(RedisStore):
logger.exception(err)
continue
user.mutuals = counts["mutuals"]
- user.shared_books = counts["shared_books"]
+ # user.shared_books = counts["shared_books"]
results.append(user)
return results
@@ -115,16 +115,16 @@ def get_annotated_users(viewer, *args, **kwargs):
),
distinct=True,
),
- shared_books=Count(
- "shelfbook",
- filter=Q(
- ~Q(id=viewer.id),
- shelfbook__book__parent_work__in=[
- s.book.parent_work for s in viewer.shelfbook_set.all()
- ],
- ),
- distinct=True,
- ),
+ # shared_books=Count(
+ # "shelfbook",
+ # filter=Q(
+ # ~Q(id=viewer.id),
+ # shelfbook__book__parent_work__in=[
+ # s.book.parent_work for s in viewer.shelfbook_set.all()
+ # ],
+ # ),
+ # distinct=True,
+ # ),
)
)
@@ -162,18 +162,18 @@ def update_suggestions_on_unfollow(sender, instance, **kwargs):
rerank_user_task.delay(instance.user_object.id, update_only=False)
-@receiver(signals.post_save, sender=models.ShelfBook)
-@receiver(signals.post_delete, sender=models.ShelfBook)
-# pylint: disable=unused-argument
-def update_rank_on_shelving(sender, instance, *args, **kwargs):
- """when a user shelves or unshelves a book, re-compute their rank"""
- # if it's a local user, re-calculate who is rec'ed to them
- if instance.user.local:
- rerank_suggestions_task.delay(instance.user.id)
-
- # if the user is discoverable, update their rankings
- if instance.user.discoverable:
- rerank_user_task.delay(instance.user.id)
+# @receiver(signals.post_save, sender=models.ShelfBook)
+# @receiver(signals.post_delete, sender=models.ShelfBook)
+# # pylint: disable=unused-argument
+# def update_rank_on_shelving(sender, instance, *args, **kwargs):
+# """when a user shelves or unshelves a book, re-compute their rank"""
+# # if it's a local user, re-calculate who is rec'ed to them
+# if instance.user.local:
+# rerank_suggestions_task.delay(instance.user.id)
+#
+# # if the user is discoverable, update their rankings
+# if instance.user.discoverable:
+# rerank_user_task.delay(instance.user.id)
@receiver(signals.post_save, sender=models.User)
diff --git a/bookwyrm/templates/author/author.html b/bookwyrm/templates/author/author.html
index 0bc42775..1c409d06 100644
--- a/bookwyrm/templates/author/author.html
+++ b/bookwyrm/templates/author/author.html
@@ -70,7 +70,7 @@
{% endif %}
-
+
{% if author.inventaire_id %}
@@ -86,7 +86,7 @@
{% endif %}
-
+
{% if author.goodreads_key %}
@@ -109,7 +109,7 @@
{% for book in books %}
- {% include 'discover/small-book.html' with book=book %}
+ {% include 'landing/small-book.html' with book=book %}
{% endfor %}
diff --git a/bookwyrm/templates/book/book.html b/bookwyrm/templates/book/book.html
index c5dab109..2e8ff0d0 100644
--- a/bookwyrm/templates/book/book.html
+++ b/bookwyrm/templates/book/book.html
@@ -72,8 +72,8 @@
{% if user_authenticated and not book.cover %}
{% trans "Add cover" as button_text %}
- {% include 'snippets/toggle/toggle_button.html' with text=button_text controls_text="add-cover" controls_uid=book.id focus="modal-title-add-cover" class="is-small" %}
- {% include 'book/cover_modal.html' with book=book controls_text="add-cover" controls_uid=book.id %}
+ {% include 'snippets/toggle/toggle_button.html' with text=button_text controls_text="add_cover" controls_uid=book.id focus="modal_title_add_cover" class="is-small" %}
+ {% include 'book/cover_modal.html' with book=book controls_text="add_cover" controls_uid=book.id %}
{% if request.GET.cover_error %}
{% trans "Failed to load cover" %}
{% endif %}
@@ -128,19 +128,19 @@
{% if user_authenticated and can_edit_book and not book|book_description %}
{% trans 'Add Description' as button_text %}
- {% include 'snippets/toggle/open_button.html' with text=button_text controls_text="add-description" controls_uid=book.id focus="id_description" hide_active=True id="hide-description" %}
+ {% include 'snippets/toggle/open_button.html' with text=button_text controls_text="add_description" controls_uid=book.id focus="id_description" hide_active=True id="hide_description" %}
-
{% trans "Add read dates" as button_text %}
- {% include 'snippets/toggle/open_button.html' with text=button_text icon_with_text="plus" class="is-small" controls_text="add-readthrough" focus="add-readthrough-focus" %}
+ {% include 'snippets/toggle/open_button.html' with text=button_text icon_with_text="plus" class="is-small" controls_text="add_readthrough" focus="add_readthrough_focus_" %}
-
+
{% include 'snippets/readthrough_form.html' with readthrough=None %}
@@ -189,7 +189,7 @@
{% trans "Cancel" as button_text %}
- {% include 'snippets/toggle/close_button.html' with text=button_text controls_text="add-readthrough" %}
+ {% include 'snippets/toggle/close_button.html' with text=button_text controls_text="add_readthrough" %}
diff --git a/bookwyrm/templates/book/readthrough.html b/bookwyrm/templates/book/readthrough.html
index 75140746..05ed3c63 100644
--- a/bookwyrm/templates/book/readthrough.html
+++ b/bookwyrm/templates/book/readthrough.html
@@ -2,7 +2,7 @@
{% load humanize %}
{% load tz %}
-
+
{% trans "Progress Updates:" %}
@@ -24,7 +24,7 @@
{% if readthrough.progress %}
{% trans "Show all updates" as button_text %}
{% include 'snippets/toggle/toggle_button.html' with text=button_text controls_text="updates" controls_uid=readthrough.id class="is-small" %}
-
+
{% for progress_update in readthrough.progress_updates %}
@@ -36,7 +36,7 @@
{{ progress_update.progress }}%
{% endif %}
-
+
{% trans "Delete this progress update" %}
@@ -57,11 +57,11 @@
{% trans "Edit read dates" as button_text %}
- {% include 'snippets/toggle/toggle_button.html' with class="is-small" text=button_text icon="pencil" controls_text="edit-readthrough" controls_uid=readthrough.id focus="edit-readthrough" %}
+ {% include 'snippets/toggle/toggle_button.html' with class="is-small" text=button_text icon="pencil" controls_text="edit_readthrough" controls_uid=readthrough.id focus="edit_readthrough" %}
{% trans "Delete these read dates" as button_text %}
- {% include 'snippets/toggle/toggle_button.html' with class="is-small" text=button_text icon="x" controls_text="delete-readthrough" controls_uid=readthrough.id focus="modal-title-delete-readthrough" %}
+ {% include 'snippets/toggle/toggle_button.html' with class="is-small" text=button_text icon="x" controls_text="delete_readthrough" controls_uid=readthrough.id focus="modal_title_delete_readthrough" %}
@@ -69,15 +69,15 @@
-
+
{% trans "Edit read dates" %}
{% include 'snippets/readthrough_form.html' with readthrough=readthrough %}
{% trans "Save" %}
{% trans "Cancel" as button_text %}
- {% include 'snippets/toggle/close_button.html' with text=button_text controls_text="edit-readthrough" controls_uid=readthrough.id %}
+ {% include 'snippets/toggle/close_button.html' with text=button_text controls_text="edit_readthrough" controls_uid=readthrough.id %}
-{% include 'snippets/delete_readthrough_modal.html' with controls_text="delete-readthrough" controls_uid=readthrough.id no_body=True %}
+{% include 'snippets/delete_readthrough_modal.html' with controls_text="delete_readthrough" controls_uid=readthrough.id no_body=True %}
diff --git a/bookwyrm/templates/components/dropdown.html b/bookwyrm/templates/components/dropdown.html
index 35caa55b..98291bc9 100644
--- a/bookwyrm/templates/components/dropdown.html
+++ b/bookwyrm/templates/components/dropdown.html
@@ -3,7 +3,7 @@
{% with 0|uuid as uuid %}