Merge pull request #2134 from bookwyrm-social/stopped-shelf-fixes

Stopped shelf fixes
This commit is contained in:
Mouse Reeve 2022-05-26 13:12:57 -07:00 committed by GitHub
commit 4e3c346780
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 105 additions and 14 deletions

View file

@ -53,7 +53,12 @@ class ReadThroughForm(CustomForm):
self.add_error( self.add_error(
"finish_date", _("Reading finish date cannot be before start date.") "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: class Meta:
model = models.ReadThrough model = models.ReadThrough
fields = ["user", "book", "start_date", "finish_date"] fields = ["user", "book", "start_date", "finish_date", "stopped_date"]

View file

@ -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 = []

View file

@ -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),
),
]

View file

@ -27,6 +27,7 @@ class ReadThrough(BookWyrmModel):
) )
start_date = models.DateTimeField(blank=True, null=True) start_date = models.DateTimeField(blank=True, null=True)
finish_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) is_active = models.BooleanField(default=True)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@ -34,7 +35,7 @@ class ReadThrough(BookWyrmModel):
cache.delete(f"latest_read_through-{self.user.id}-{self.book.id}") cache.delete(f"latest_read_through-{self.user.id}-{self.book.id}")
self.user.update_active_date() self.user.update_active_date()
# an active readthrough must have an unset finish 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 self.is_active = False
super().save(*args, **kwargs) super().save(*args, **kwargs)

View file

@ -11,7 +11,7 @@ from django.utils.translation import gettext_lazy as _
env = Env() env = Env()
env.read_env() env.read_env()
DOMAIN = env("DOMAIN") DOMAIN = env("DOMAIN")
VERSION = "0.3.4" VERSION = "0.4.0"
RELEASE_API = env( RELEASE_API = env(
"RELEASE_API", "RELEASE_API",
@ -21,7 +21,7 @@ RELEASE_API = env(
PAGE_LENGTH = env("PAGE_LENGTH", 15) PAGE_LENGTH = env("PAGE_LENGTH", 15)
DEFAULT_LANGUAGE = env("DEFAULT_LANGUAGE", "English") DEFAULT_LANGUAGE = env("DEFAULT_LANGUAGE", "English")
JS_CACHE = "bc93172a" JS_CACHE = "e678183b"
# email # email
EMAIL_BACKEND = env("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend") EMAIL_BACKEND = env("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend")

View file

@ -203,6 +203,8 @@ let StatusCache = new (class {
.forEach((item) => (item.disabled = false)); .forEach((item) => (item.disabled = false));
next_identifier = next_identifier == "complete" ? "read" : next_identifier; next_identifier = next_identifier == "complete" ? "read" : next_identifier;
next_identifier =
next_identifier == "stopped-reading-complete" ? "stopped-reading" : next_identifier;
// Disable the current state // Disable the current state
button.querySelector( button.querySelector(

View file

@ -19,6 +19,7 @@
</label> </label>
{% include "snippets/progress_field.html" with id=field_id %} {% include "snippets/progress_field.html" with id=field_id %}
{% endif %} {% endif %}
<div class="field"> <div class="field">
<label class="label" for="id_finish_date_{{ readthrough.id }}"> <label class="label" for="id_finish_date_{{ readthrough.id }}">
{% trans "Finished reading" %} {% trans "Finished reading" %}

View file

@ -8,10 +8,12 @@
<div class="column"> <div class="column">
{% trans "Progress Updates:" %} {% trans "Progress Updates:" %}
<ul> <ul>
{% if readthrough.finish_date or readthrough.progress %} {% if readthrough.finish_date or readthrough.stopped_date or readthrough.progress %}
<li> <li>
{% if readthrough.finish_date %} {% if readthrough.finish_date %}
{{ readthrough.finish_date | localtime | naturalday }}: {% trans "finished" %} {{ readthrough.finish_date | localtime | naturalday }}: {% trans "finished" %}
{% elif readthrough.stopped_date %}
{{ readthrough.stopped_date | localtime | naturalday }}: {% trans "stopped" %}
{% else %} {% else %}
{% if readthrough.progress_mode == 'PG' %} {% if readthrough.progress_mode == 'PG' %}

View file

@ -12,7 +12,7 @@ Stop Reading "<em>{{ book_title }}</em>"
<form name="stop-reading-{{ uuid }}" action="{% url 'reading-status' 'stop' book.id %}" method="post" {% if not refresh %}class="submit-status"{% endif %}> <form name="stop-reading-{{ uuid }}" action="{% url 'reading-status' 'stop' book.id %}" method="post" {% if not refresh %}class="submit-status"{% endif %}>
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="id" value="{{ readthrough.id }}"> <input type="hidden" name="id" value="{{ readthrough.id }}">
<input type="hidden" name="reading_status" value="stop"> <input type="hidden" name="reading_status" value="stopped-reading">
<input type="hidden" name="shelf" value="{{ move_from }}"> <input type="hidden" name="shelf" value="{{ move_from }}">
{% endblock %} {% endblock %}
@ -29,9 +29,9 @@ Stop Reading "<em>{{ book_title }}</em>"
<div class="column is-half"> <div class="column is-half">
<div class="field"> <div class="field">
<label class="label" for="id_read_until_date_{{ uuid }}"> <label class="label" for="id_read_until_date_{{ uuid }}">
{% trans "Read until" %} {% trans "Stopped reading" %}
</label> </label>
<input type="date" name="finish_date" class="input" id="id_read_until_date_{{ uuid }}" value="{% now "Y-m-d" %}"> <input type="date" name="stopped_date" class="input" id="id_read_until_date_{{ uuid }}" value="{% now "Y-m-d" %}">
</div> </div>
</div> </div>
</div> </div>

View file

@ -8,7 +8,7 @@
{% comparison_bool shelf.identifier active_shelf.shelf.identifier as is_current %} {% comparison_bool shelf.identifier active_shelf.shelf.identifier as is_current %}
<li role="menuitem" class="dropdown-item p-0"> <li role="menuitem" class="dropdown-item p-0">
<div <div
class="{% if next_shelf_identifier == shelf.identifier %}is-hidden{% endif %}" class="{% if is_current or next_shelf_identifier == shelf.identifier %}is-hidden{% elif shelf.identifier == 'stopped-reading' and active_shelf.shelf.identifier != "reading" %}is-hidden{% endif %}"
data-shelf-dropdown-identifier="{{ shelf.identifier }}" data-shelf-dropdown-identifier="{{ shelf.identifier }}"
data-shelf-next="{{ shelf.identifier|next_shelf }}" data-shelf-next="{{ shelf.identifier|next_shelf }}"
> >

View file

@ -13,6 +13,15 @@
</button> </button>
</div> </div>
<div
class="{% if next_shelf_identifier != 'stopped-reading-complete' %}is-hidden{% endif %}"
data-shelf-identifier="stopped-reading-complete"
>
<button type="button" class="button {{ class }}" disabled>
<span>{% trans "Stopped reading" %}</span>
</button>
</div>
{% for shelf in shelves %} {% for shelf in shelves %}
<div <div
class="{% if next_shelf_identifier != shelf.identifier %}is-hidden{% endif %}" class="{% if next_shelf_identifier != shelf.identifier %}is-hidden{% endif %}"
@ -33,10 +42,11 @@
{% join "finish_reading" button_uuid as modal_id %} {% join "finish_reading" button_uuid as modal_id %}
{% include 'snippets/shelve_button/modal_button.html' with class=class fallback_url=fallback_url %} {% include 'snippets/shelve_button/modal_button.html' with class=class fallback_url=fallback_url %}
{% elif shelf.identifier == 'stopped-reading' %} {% elif shelf.identifier == 'stopped-reading' %}
{% trans "Stop reading" as button_text %} {% trans "Stop reading" as button_text %}
{% url 'reading-status' 'stop' book.id as fallback_url %} {% url 'reading-status' 'finish' book.id as fallback_url %}
{% join "stop_reading" 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 %} {% include 'snippets/shelve_button/modal_button.html' with class=class fallback_url=fallback_url %}

View file

@ -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 <a href="{{ book_path }}">{{ book }}</a> by <a href="{{ author_path }}">{{ author_name }}</a>
{% endblocktrans %}
{% endwith %}
{% else %}
{% blocktrans trimmed with book_path=book.local_path book=book|book_title %}
stopped reading <a href="{{ book_path }}">{{ book }}</a>
{% endblocktrans %}
{% endif %}
{% endspaceless %}

View file

@ -30,6 +30,8 @@ def get_next_shelf(current_shelf):
return "read" return "read"
if current_shelf == "read": if current_shelf == "read":
return "complete" return "complete"
if current_shelf == "stopped-reading":
return "stopped-reading-complete"
return "to-read" return "to-read"

View file

@ -1,4 +1,5 @@
""" the good stuff! the books! """ """ the good stuff! the books! """
import logging
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.cache import cache from django.core.cache import cache
from django.db import transaction 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 get_edition, handle_reading_status, is_api_request
from .helpers import load_date_in_user_tz_as_utc from .helpers import load_date_in_user_tz_as_utc
logger = logging.getLogger(__name__)
# pylint: disable=no-self-use # pylint: disable=no-self-use
# pylint: disable=too-many-return-statements # pylint: disable=too-many-return-statements
@ -36,6 +39,7 @@ class ReadingStatus(View):
# redirect if we're already on this shelf # redirect if we're already on this shelf
return TemplateResponse(request, f"reading_progress/{template}", {"book": book}) return TemplateResponse(request, f"reading_progress/{template}", {"book": book})
@transaction.atomic
def post(self, request, status, book_id): def post(self, request, status, book_id):
"""Change the state of a book by shelving it and adding reading dates""" """Change the state of a book by shelving it and adding reading dates"""
identifier = { identifier = {
@ -45,6 +49,7 @@ class ReadingStatus(View):
"stop": models.Shelf.STOPPED_READING, "stop": models.Shelf.STOPPED_READING,
}.get(status) }.get(status)
if not identifier: if not identifier:
logger.exception("Invalid reading status type: %s", status)
return HttpResponseBadRequest() return HttpResponseBadRequest()
# invalidate related caches # invalidate related caches
@ -87,6 +92,7 @@ class ReadingStatus(View):
desired_shelf.identifier, desired_shelf.identifier,
start_date=request.POST.get("start_date"), start_date=request.POST.get("start_date"),
finish_date=request.POST.get("finish_date"), finish_date=request.POST.get("finish_date"),
stopped_date=request.POST.get("stopped_date"),
) )
# post about it (if you want) # post about it (if you want)
@ -155,8 +161,9 @@ class ReadThrough(View):
@transaction.atomic @transaction.atomic
# pylint: disable=too-many-arguments
def update_readthrough_on_shelve( def update_readthrough_on_shelve(
user, annotated_book, status, start_date=None, finish_date=None user, annotated_book, status, start_date=None, finish_date=None, stopped_date=None
): ):
"""update the current readthrough for a book when it is re-shelved""" """update the current readthrough for a book when it is re-shelved"""
# there *should* only be one of current active readthrough, but it's a list # there *should* only be one of current active readthrough, but it's a list
@ -178,8 +185,9 @@ def update_readthrough_on_shelve(
) )
# santiize and set dates # santiize and set dates
active_readthrough.start_date = load_date_in_user_tz_as_utc(start_date, user) active_readthrough.start_date = load_date_in_user_tz_as_utc(start_date, user)
# if the finish date is set, the readthrough will be automatically set as inactive # if the stop or finish date is set, the readthrough will be set as inactive
active_readthrough.finish_date = load_date_in_user_tz_as_utc(finish_date, user) active_readthrough.finish_date = load_date_in_user_tz_as_utc(finish_date, user)
active_readthrough.stopped_date = load_date_in_user_tz_as_utc(stopped_date, user)
active_readthrough.save() active_readthrough.save()

View file

@ -1,5 +1,6 @@
""" what are we here for if not for posting """ """ what are we here for if not for posting """
import re import re
import logging
from urllib.parse import urlparse from urllib.parse import urlparse
from django.contrib.auth.decorators import login_required 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 handle_remote_webfinger, is_api_request
from .helpers import load_date_in_user_tz_as_utc from .helpers import load_date_in_user_tz_as_utc
logger = logging.getLogger(__name__)
# pylint: disable= no-self-use # pylint: disable= no-self-use
@method_decorator(login_required, name="dispatch") @method_decorator(login_required, name="dispatch")
@ -72,11 +75,14 @@ class CreateStatus(View):
form = getattr(forms, f"{status_type}Form")( form = getattr(forms, f"{status_type}Form")(
request.POST, instance=existing_status request.POST, instance=existing_status
) )
except AttributeError: except AttributeError as err:
logger.exception(err)
return HttpResponseBadRequest() return HttpResponseBadRequest()
if not form.is_valid(): if not form.is_valid():
if is_api_request(request): if is_api_request(request):
return HttpResponse(status=500) logger.exception(form.errors)
return HttpResponseBadRequest()
return redirect(request.headers.get("Referer", "/")) return redirect(request.headers.get("Referer", "/"))
status = form.save(commit=False) status = form.save(commit=False)