forked from mirrors/bookwyrm
Merge branch 'main' into production
This commit is contained in:
commit
b1f3253aa7
20 changed files with 183 additions and 27 deletions
|
@ -16,7 +16,7 @@ If you'd like to join an instance, you can check out the [instances](https://joi
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
See [contributing](https://docs.joinbookwyrm.com/how-to-contribute.html) for code, translation or monetary contributions.
|
See [contributing](https://docs.joinbookwyrm.com/contributing.html) for code, translation or monetary contributions.
|
||||||
|
|
||||||
## About BookWyrm
|
## About BookWyrm
|
||||||
### What it is and isn't
|
### What it is and isn't
|
||||||
|
@ -76,4 +76,4 @@ Deployment
|
||||||
|
|
||||||
|
|
||||||
## Set up BookWyrm
|
## Set up BookWyrm
|
||||||
The [documentation website](https://docs.joinbookwyrm.com/) has instruction on how to set up BookWyrm in a [developer environment](https://docs.joinbookwyrm.com/developer-environment.html) or [production](https://docs.joinbookwyrm.com/installing-in-production.html).
|
The [documentation website](https://docs.joinbookwyrm.com/) has instruction on how to set up BookWyrm in a [developer environment](https://docs.joinbookwyrm.com/install-dev.html) or [production](https://docs.joinbookwyrm.com/install-prod.html).
|
||||||
|
|
|
@ -298,8 +298,9 @@ def add_status_on_create_command(sender, instance, created):
|
||||||
priority = HIGH
|
priority = HIGH
|
||||||
# check if this is an old status, de-prioritize if so
|
# 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)
|
# (this will happen if federation is very slow, or, more expectedly, on csv import)
|
||||||
one_day = 60 * 60 * 24
|
if instance.published_date < timezone.now() - timedelta(
|
||||||
if (instance.created_date - instance.published_date).seconds > one_day:
|
days=1
|
||||||
|
) or instance.created_date < instance.published_date - timedelta(days=1):
|
||||||
priority = LOW
|
priority = LOW
|
||||||
|
|
||||||
add_status_task.apply_async(
|
add_status_task.apply_async(
|
||||||
|
|
|
@ -8,6 +8,7 @@ from django.db.models import Q
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.utils.text import slugify
|
||||||
|
|
||||||
from bookwyrm.settings import DOMAIN
|
from bookwyrm.settings import DOMAIN
|
||||||
from .fields import RemoteIdField
|
from .fields import RemoteIdField
|
||||||
|
@ -35,10 +36,11 @@ class BookWyrmModel(models.Model):
|
||||||
remote_id = RemoteIdField(null=True, activitypub_field="id")
|
remote_id = RemoteIdField(null=True, activitypub_field="id")
|
||||||
|
|
||||||
def get_remote_id(self):
|
def get_remote_id(self):
|
||||||
"""generate a url that resolves to the local object"""
|
"""generate the url that resolves to the local object, without a slug"""
|
||||||
base_path = f"https://{DOMAIN}"
|
base_path = f"https://{DOMAIN}"
|
||||||
if hasattr(self, "user"):
|
if hasattr(self, "user"):
|
||||||
base_path = f"{base_path}{self.user.local_path}"
|
base_path = f"{base_path}{self.user.local_path}"
|
||||||
|
|
||||||
model_name = type(self).__name__.lower()
|
model_name = type(self).__name__.lower()
|
||||||
return f"{base_path}/{model_name}/{self.id}"
|
return f"{base_path}/{model_name}/{self.id}"
|
||||||
|
|
||||||
|
@ -49,8 +51,20 @@ class BookWyrmModel(models.Model):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def local_path(self):
|
def local_path(self):
|
||||||
"""how to link to this object in the local app"""
|
"""how to link to this object in the local app, with a slug"""
|
||||||
return self.get_remote_id().replace(f"https://{DOMAIN}", "")
|
local = self.get_remote_id().replace(f"https://{DOMAIN}", "")
|
||||||
|
|
||||||
|
name = None
|
||||||
|
if hasattr(self, "name_field"):
|
||||||
|
name = getattr(self, self.name_field)
|
||||||
|
elif hasattr(self, "name"):
|
||||||
|
name = self.name
|
||||||
|
|
||||||
|
if name:
|
||||||
|
slug = slugify(name)
|
||||||
|
local = f"{local}/s/{slug}"
|
||||||
|
|
||||||
|
return local
|
||||||
|
|
||||||
def raise_visible_to_user(self, viewer):
|
def raise_visible_to_user(self, viewer):
|
||||||
"""is a user authorized to view an object?"""
|
"""is a user authorized to view an object?"""
|
||||||
|
|
|
@ -6,6 +6,7 @@ from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from bookwyrm import activitypub
|
from bookwyrm import activitypub
|
||||||
|
from bookwyrm.settings import DOMAIN
|
||||||
from .activitypub_mixin import CollectionItemMixin, OrderedCollectionMixin
|
from .activitypub_mixin import CollectionItemMixin, OrderedCollectionMixin
|
||||||
from .base_model import BookWyrmModel
|
from .base_model import BookWyrmModel
|
||||||
from . import fields
|
from . import fields
|
||||||
|
@ -65,6 +66,11 @@ class Shelf(OrderedCollectionMixin, BookWyrmModel):
|
||||||
identifier = self.identifier or self.get_identifier()
|
identifier = self.identifier or self.get_identifier()
|
||||||
return f"{base_path}/books/{identifier}"
|
return f"{base_path}/books/{identifier}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def local_path(self):
|
||||||
|
"""No slugs"""
|
||||||
|
return self.get_remote_id().replace(f"https://{DOMAIN}", "")
|
||||||
|
|
||||||
def raise_not_deletable(self, viewer):
|
def raise_not_deletable(self, viewer):
|
||||||
"""don't let anyone delete a default shelf"""
|
"""don't let anyone delete a default shelf"""
|
||||||
super().raise_not_deletable(viewer)
|
super().raise_not_deletable(viewer)
|
||||||
|
|
|
@ -284,7 +284,7 @@
|
||||||
{% if user_statuses.review_count or user_statuses.comment_count or user_statuses.quotation_count %}
|
{% if user_statuses.review_count or user_statuses.comment_count or user_statuses.quotation_count %}
|
||||||
<nav class="tabs">
|
<nav class="tabs">
|
||||||
<ul>
|
<ul>
|
||||||
{% url 'book' book.id as tab_url %}
|
{% url 'book' book.id book.name|slugify as tab_url %}
|
||||||
<li {% if tab_url == request.path %}class="is-active"{% endif %}>
|
<li {% if tab_url == request.path %}class="is-active"{% endif %}>
|
||||||
<a href="{{ tab_url }}#reviews">{% trans "Reviews" %} ({{ review_count }})</a>
|
<a href="{{ tab_url }}#reviews">{% trans "Reviews" %} ({{ review_count }})</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
<nav class="breadcrumb subtitle" aria-label="breadcrumbs">
|
<nav class="breadcrumb subtitle" aria-label="breadcrumbs">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="{% url 'book' book.id %}">{{ book|book_title }}</a></li>
|
<li><a href="{% url 'book' book.id book.name|slugify %}">{{ book|book_title }}</a></li>
|
||||||
<li class="is-active">
|
<li class="is-active">
|
||||||
<a href="#" aria-current="page">
|
<a href="#" aria-current="page">
|
||||||
{% trans "Edit links" %}
|
{% trans "Edit links" %}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<nav class="breadcrumb subtitle" aria-label="breadcrumbs">
|
<nav class="breadcrumb subtitle" aria-label="breadcrumbs">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="{% url 'lists' %}">{% trans "Lists" %}</a></li>
|
<li><a href="{% url 'lists' %}">{% trans "Lists" %}</a></li>
|
||||||
<li><a href="{% url 'list' list.id %}">{{ list.name|truncatechars:30 }}</a></li>
|
<li><a href="{% url 'list' list_id=list.id slug=list.name|slugify %}">{{ list.name|truncatechars:30 }}</a></li>
|
||||||
<li class="is-active">
|
<li class="is-active">
|
||||||
<a href="#" aria-current="page">
|
<a href="#" aria-current="page">
|
||||||
{% trans "Curate" %}
|
{% trans "Curate" %}
|
||||||
|
|
|
@ -180,7 +180,7 @@
|
||||||
<h2 class="title is-5">
|
<h2 class="title is-5">
|
||||||
{% trans "Sort List" %}
|
{% trans "Sort List" %}
|
||||||
</h2>
|
</h2>
|
||||||
<form name="sort" action="{% url 'list' list.id %}" method="GET" class="block">
|
<form name="sort" action="{% url 'list' list_id=list.id slug=list.name|slugify %}" method="GET" class="block">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="id_sort_by">{% trans "Sort By" %}</label>
|
<label class="label" for="id_sort_by">{% trans "Sort By" %}</label>
|
||||||
<div class="select is-fullwidth">
|
<div class="select is-fullwidth">
|
||||||
|
@ -207,7 +207,7 @@
|
||||||
{% trans "Suggest Books" %}
|
{% trans "Suggest Books" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h2>
|
</h2>
|
||||||
<form name="search" action="{% url 'list' list.id %}" method="GET" class="block">
|
<form name="search" action="{% url 'list' list_id=list.id slug=list.name|slugify %}" method="GET" class="block">
|
||||||
<div class="field has-addons">
|
<div class="field has-addons">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input aria-label="{% trans 'Search for a book' %}" class="input" type="text" name="q" placeholder="{% trans 'Search for a book' %}" value="{{ query }}">
|
<input aria-label="{% trans 'Search for a book' %}" class="input" type="text" name="q" placeholder="{% trans 'Search for a book' %}" value="{{ query }}">
|
||||||
|
@ -221,7 +221,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if query %}
|
{% if query %}
|
||||||
<p class="help"><a href="{% url 'list' list.id %}">{% trans "Clear search" %}</a></p>
|
<p class="help"><a href="{% url 'list' list_id=list.id slug=list.name|slugify %}">{% trans "Clear search" %}</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
{% if not suggested_books %}
|
{% if not suggested_books %}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
""" testing activitystreams """
|
""" testing activitystreams """
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from bookwyrm import activitystreams, models
|
from bookwyrm import activitystreams, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,6 +66,39 @@ class ActivitystreamsSignals(TestCase):
|
||||||
self.assertEqual(args["args"][0], status.id)
|
self.assertEqual(args["args"][0], status.id)
|
||||||
self.assertEqual(args["queue"], "high_priority")
|
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, *_):
|
def test_populate_streams_on_account_create_command(self, *_):
|
||||||
"""create streams for a user"""
|
"""create streams for a user"""
|
||||||
with patch("bookwyrm.activitystreams.populate_stream_task.delay") as mock:
|
with patch("bookwyrm.activitystreams.populate_stream_task.delay") as mock:
|
||||||
|
|
|
@ -101,6 +101,7 @@ class AbstractConnector(TestCase):
|
||||||
result = self.connector.get_or_create_book(
|
result = self.connector.get_or_create_book(
|
||||||
f"https://{DOMAIN}/book/{self.book.id}"
|
f"https://{DOMAIN}/book/{self.book.id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(models.Book.objects.count(), 1)
|
self.assertEqual(models.Book.objects.count(), 1)
|
||||||
self.assertEqual(result, self.book)
|
self.assertEqual(result, self.book)
|
||||||
|
|
||||||
|
|
|
@ -391,6 +391,9 @@ urlpatterns = [
|
||||||
re_path(
|
re_path(
|
||||||
r"^group/(?P<group_id>\d+)(.json)?/?$", views.Group.as_view(), name="group"
|
r"^group/(?P<group_id>\d+)(.json)?/?$", views.Group.as_view(), name="group"
|
||||||
),
|
),
|
||||||
|
re_path(
|
||||||
|
rf"^group/(?P<group_id>\d+){regex.SLUG}/?$", views.Group.as_view(), name="group"
|
||||||
|
),
|
||||||
re_path(
|
re_path(
|
||||||
r"^group/delete/(?P<group_id>\d+)/?$", views.delete_group, name="delete-group"
|
r"^group/delete/(?P<group_id>\d+)/?$", views.delete_group, name="delete-group"
|
||||||
),
|
),
|
||||||
|
@ -417,7 +420,10 @@ urlpatterns = [
|
||||||
re_path(rf"{USER_PATH}/lists/?$", views.UserLists.as_view(), name="user-lists"),
|
re_path(rf"{USER_PATH}/lists/?$", views.UserLists.as_view(), name="user-lists"),
|
||||||
re_path(r"^list/?$", views.Lists.as_view(), name="lists"),
|
re_path(r"^list/?$", views.Lists.as_view(), name="lists"),
|
||||||
re_path(r"^list/saved/?$", views.SavedLists.as_view(), name="saved-lists"),
|
re_path(r"^list/saved/?$", views.SavedLists.as_view(), name="saved-lists"),
|
||||||
re_path(r"^list/(?P<list_id>\d+)(.json)?/?$", views.List.as_view(), name="list"),
|
re_path(r"^list/(?P<list_id>\d+)(\.json)?/?$", views.List.as_view(), name="list"),
|
||||||
|
re_path(
|
||||||
|
rf"^list/(?P<list_id>\d+){regex.SLUG}/?$", views.List.as_view(), name="list"
|
||||||
|
),
|
||||||
re_path(
|
re_path(
|
||||||
r"^list/(?P<list_id>\d+)/item/(?P<list_item>\d+)/?$",
|
r"^list/(?P<list_id>\d+)/item/(?P<list_item>\d+)/?$",
|
||||||
views.ListItem.as_view(),
|
views.ListItem.as_view(),
|
||||||
|
@ -487,6 +493,7 @@ urlpatterns = [
|
||||||
re_path(r"^unblock/(?P<user_id>\d+)/?$", views.unblock),
|
re_path(r"^unblock/(?P<user_id>\d+)/?$", views.unblock),
|
||||||
# statuses
|
# statuses
|
||||||
re_path(rf"{STATUS_PATH}(.json)?/?$", views.Status.as_view(), name="status"),
|
re_path(rf"{STATUS_PATH}(.json)?/?$", views.Status.as_view(), name="status"),
|
||||||
|
re_path(rf"{STATUS_PATH}{regex.SLUG}/?$", views.Status.as_view(), name="status"),
|
||||||
re_path(rf"{STATUS_PATH}/activity/?$", views.Status.as_view(), name="status"),
|
re_path(rf"{STATUS_PATH}/activity/?$", views.Status.as_view(), name="status"),
|
||||||
re_path(
|
re_path(
|
||||||
rf"{STATUS_PATH}/replies(.json)?/?$", views.Replies.as_view(), name="replies"
|
rf"{STATUS_PATH}/replies(.json)?/?$", views.Replies.as_view(), name="replies"
|
||||||
|
@ -523,6 +530,7 @@ urlpatterns = [
|
||||||
re_path(r"^unboost/(?P<status_id>\d+)/?$", views.Unboost.as_view()),
|
re_path(r"^unboost/(?P<status_id>\d+)/?$", views.Unboost.as_view()),
|
||||||
# books
|
# books
|
||||||
re_path(rf"{BOOK_PATH}(.json)?/?$", views.Book.as_view(), name="book"),
|
re_path(rf"{BOOK_PATH}(.json)?/?$", views.Book.as_view(), name="book"),
|
||||||
|
re_path(rf"{BOOK_PATH}{regex.SLUG}/?$", views.Book.as_view(), name="book"),
|
||||||
re_path(
|
re_path(
|
||||||
rf"{BOOK_PATH}/(?P<user_statuses>review|comment|quote)/?$",
|
rf"{BOOK_PATH}/(?P<user_statuses>review|comment|quote)/?$",
|
||||||
views.Book.as_view(),
|
views.Book.as_view(),
|
||||||
|
@ -580,6 +588,11 @@ urlpatterns = [
|
||||||
re_path(
|
re_path(
|
||||||
r"^author/(?P<author_id>\d+)(.json)?/?$", views.Author.as_view(), name="author"
|
r"^author/(?P<author_id>\d+)(.json)?/?$", views.Author.as_view(), name="author"
|
||||||
),
|
),
|
||||||
|
re_path(
|
||||||
|
rf"^author/(?P<author_id>\d+){regex.SLUG}/?$",
|
||||||
|
views.Author.as_view(),
|
||||||
|
name="author",
|
||||||
|
),
|
||||||
re_path(
|
re_path(
|
||||||
r"^author/(?P<author_id>\d+)/edit/?$",
|
r"^author/(?P<author_id>\d+)/edit/?$",
|
||||||
views.EditAuthor.as_view(),
|
views.EditAuthor.as_view(),
|
||||||
|
|
|
@ -6,5 +6,6 @@ STRICT_LOCALNAME = r"@[a-zA-Z_\-\.0-9]+"
|
||||||
USERNAME = rf"{LOCALNAME}(@{DOMAIN})?"
|
USERNAME = rf"{LOCALNAME}(@{DOMAIN})?"
|
||||||
STRICT_USERNAME = rf"\B{STRICT_LOCALNAME}(@{DOMAIN})?\b"
|
STRICT_USERNAME = rf"\B{STRICT_LOCALNAME}(@{DOMAIN})?\b"
|
||||||
FULL_USERNAME = rf"{LOCALNAME}@{DOMAIN}\b"
|
FULL_USERNAME = rf"{LOCALNAME}@{DOMAIN}\b"
|
||||||
|
SLUG = r"/s/(?P<slug>[-_a-z0-9]*)"
|
||||||
# should match (BookWyrm/1.0.0; or (BookWyrm/99.1.2;
|
# should match (BookWyrm/1.0.0; or (BookWyrm/99.1.2;
|
||||||
BOOKWYRM_USER_AGENT = r"\(BookWyrm/[0-9]+\.[0-9]+\.[0-9]+;"
|
BOOKWYRM_USER_AGENT = r"\(BookWyrm/[0-9]+\.[0-9]+\.[0-9]+;"
|
||||||
|
|
|
@ -11,20 +11,24 @@ from bookwyrm import forms, models
|
||||||
from bookwyrm.activitypub import ActivitypubResponse
|
from bookwyrm.activitypub import ActivitypubResponse
|
||||||
from bookwyrm.connectors import connector_manager
|
from bookwyrm.connectors import connector_manager
|
||||||
from bookwyrm.settings import PAGE_LENGTH
|
from bookwyrm.settings import PAGE_LENGTH
|
||||||
from bookwyrm.views.helpers import is_api_request
|
from bookwyrm.views.helpers import is_api_request, maybe_redirect_local_path
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable= no-self-use
|
# pylint: disable= no-self-use
|
||||||
class Author(View):
|
class Author(View):
|
||||||
"""this person wrote a book"""
|
"""this person wrote a book"""
|
||||||
|
|
||||||
def get(self, request, author_id):
|
# pylint: disable=unused-argument
|
||||||
|
def get(self, request, author_id, slug=None):
|
||||||
"""landing page for an author"""
|
"""landing page for an author"""
|
||||||
author = get_object_or_404(models.Author, id=author_id)
|
author = get_object_or_404(models.Author, id=author_id)
|
||||||
|
|
||||||
if is_api_request(request):
|
if is_api_request(request):
|
||||||
return ActivitypubResponse(author.to_activity())
|
return ActivitypubResponse(author.to_activity())
|
||||||
|
|
||||||
|
if redirect_local_path := maybe_redirect_local_path(request, author):
|
||||||
|
return redirect_local_path
|
||||||
|
|
||||||
books = (
|
books = (
|
||||||
models.Work.objects.filter(editions__authors=author)
|
models.Work.objects.filter(editions__authors=author)
|
||||||
.order_by("created_date")
|
.order_by("created_date")
|
||||||
|
|
|
@ -15,14 +15,14 @@ from bookwyrm.activitypub import ActivitypubResponse
|
||||||
from bookwyrm.connectors import connector_manager, ConnectorException
|
from bookwyrm.connectors import connector_manager, ConnectorException
|
||||||
from bookwyrm.connectors.abstract_connector import get_image
|
from bookwyrm.connectors.abstract_connector import get_image
|
||||||
from bookwyrm.settings import PAGE_LENGTH
|
from bookwyrm.settings import PAGE_LENGTH
|
||||||
from bookwyrm.views.helpers import is_api_request
|
from bookwyrm.views.helpers import is_api_request, maybe_redirect_local_path
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
class Book(View):
|
class Book(View):
|
||||||
"""a book! this is the stuff"""
|
"""a book! this is the stuff"""
|
||||||
|
|
||||||
def get(self, request, book_id, user_statuses=False, update_error=False):
|
def get(self, request, book_id, **kwargs):
|
||||||
"""info about a book"""
|
"""info about a book"""
|
||||||
if is_api_request(request):
|
if is_api_request(request):
|
||||||
book = get_object_or_404(
|
book = get_object_or_404(
|
||||||
|
@ -30,7 +30,11 @@ class Book(View):
|
||||||
)
|
)
|
||||||
return ActivitypubResponse(book.to_activity())
|
return ActivitypubResponse(book.to_activity())
|
||||||
|
|
||||||
user_statuses = user_statuses if request.user.is_authenticated else False
|
user_statuses = (
|
||||||
|
kwargs.get("user_statuses", False)
|
||||||
|
if request.user.is_authenticated
|
||||||
|
else False
|
||||||
|
)
|
||||||
|
|
||||||
# it's safe to use this OR because edition and work and subclasses of the same
|
# it's safe to use this OR because edition and work and subclasses of the same
|
||||||
# table, so they never have clashing IDs
|
# table, so they never have clashing IDs
|
||||||
|
@ -46,6 +50,11 @@ class Book(View):
|
||||||
if not book or not book.parent_work:
|
if not book or not book.parent_work:
|
||||||
raise Http404()
|
raise Http404()
|
||||||
|
|
||||||
|
if redirect_local_path := not user_statuses and maybe_redirect_local_path(
|
||||||
|
request, book
|
||||||
|
):
|
||||||
|
return redirect_local_path
|
||||||
|
|
||||||
# all reviews for all editions of the book
|
# all reviews for all editions of the book
|
||||||
reviews = models.Review.privacy_filter(request.user).filter(
|
reviews = models.Review.privacy_filter(request.user).filter(
|
||||||
book__parent_work__editions=book
|
book__parent_work__editions=book
|
||||||
|
@ -80,7 +89,7 @@ class Book(View):
|
||||||
else None,
|
else None,
|
||||||
"rating": reviews.aggregate(Avg("rating"))["rating__avg"],
|
"rating": reviews.aggregate(Avg("rating"))["rating__avg"],
|
||||||
"lists": lists,
|
"lists": lists,
|
||||||
"update_error": update_error,
|
"update_error": kwargs.get("update_error", False),
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
|
|
|
@ -15,7 +15,7 @@ from bookwyrm.activitypub import ActivitypubResponse
|
||||||
from bookwyrm.settings import PAGE_LENGTH, STREAMS
|
from bookwyrm.settings import PAGE_LENGTH, STREAMS
|
||||||
from bookwyrm.suggested_users import suggested_users
|
from bookwyrm.suggested_users import suggested_users
|
||||||
from .helpers import filter_stream_by_status_type, get_user_from_username
|
from .helpers import filter_stream_by_status_type, get_user_from_username
|
||||||
from .helpers import is_api_request, is_bookwyrm_request
|
from .helpers import is_api_request, is_bookwyrm_request, maybe_redirect_local_path
|
||||||
from .annual_summary import get_annual_summary_year
|
from .annual_summary import get_annual_summary_year
|
||||||
|
|
||||||
|
|
||||||
|
@ -113,7 +113,8 @@ class DirectMessage(View):
|
||||||
class Status(View):
|
class Status(View):
|
||||||
"""get posting"""
|
"""get posting"""
|
||||||
|
|
||||||
def get(self, request, username, status_id):
|
# pylint: disable=unused-argument
|
||||||
|
def get(self, request, username, status_id, slug=None):
|
||||||
"""display a particular status (and replies, etc)"""
|
"""display a particular status (and replies, etc)"""
|
||||||
user = get_user_from_username(request.user, username)
|
user = get_user_from_username(request.user, username)
|
||||||
status = get_object_or_404(
|
status = get_object_or_404(
|
||||||
|
@ -130,6 +131,9 @@ class Status(View):
|
||||||
status.to_activity(pure=not is_bookwyrm_request(request))
|
status.to_activity(pure=not is_bookwyrm_request(request))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if redirect_local_path := maybe_redirect_local_path(request, status):
|
||||||
|
return redirect_local_path
|
||||||
|
|
||||||
visible_thread = (
|
visible_thread = (
|
||||||
models.Status.privacy_filter(request.user)
|
models.Status.privacy_filter(request.user)
|
||||||
.filter(thread_id=status.thread_id)
|
.filter(thread_id=status.thread_id)
|
||||||
|
|
|
@ -14,17 +14,22 @@ from django.db.models.functions import Greatest
|
||||||
|
|
||||||
from bookwyrm import forms, models
|
from bookwyrm import forms, models
|
||||||
from bookwyrm.suggested_users import suggested_users
|
from bookwyrm.suggested_users import suggested_users
|
||||||
from .helpers import get_user_from_username
|
from .helpers import get_user_from_username, maybe_redirect_local_path
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
class Group(View):
|
class Group(View):
|
||||||
"""group page"""
|
"""group page"""
|
||||||
|
|
||||||
def get(self, request, group_id):
|
# pylint: disable=unused-argument
|
||||||
|
def get(self, request, group_id, slug=None):
|
||||||
"""display a group"""
|
"""display a group"""
|
||||||
|
|
||||||
group = get_object_or_404(models.Group, id=group_id)
|
group = get_object_or_404(models.Group, id=group_id)
|
||||||
group.raise_visible_to_user(request.user)
|
group.raise_visible_to_user(request.user)
|
||||||
|
|
||||||
|
if redirect_local_path := maybe_redirect_local_path(request, group):
|
||||||
|
return redirect_local_path
|
||||||
|
|
||||||
lists = (
|
lists = (
|
||||||
models.List.privacy_filter(request.user)
|
models.List.privacy_filter(request.user)
|
||||||
.filter(group=group)
|
.filter(group=group)
|
||||||
|
@ -80,7 +85,8 @@ class Group(View):
|
||||||
class UserGroups(View):
|
class UserGroups(View):
|
||||||
"""a user's groups page"""
|
"""a user's groups page"""
|
||||||
|
|
||||||
def get(self, request, username):
|
# pylint: disable=unused-argument
|
||||||
|
def get(self, request, username, slug=None):
|
||||||
"""display a group"""
|
"""display a group"""
|
||||||
user = get_user_from_username(request.user, username)
|
user = get_user_from_username(request.user, username)
|
||||||
groups = (
|
groups = (
|
||||||
|
|
|
@ -8,6 +8,7 @@ from dateutil.parser import ParserError
|
||||||
from requests import HTTPError
|
from requests import HTTPError
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.conf import settings as django_settings
|
from django.conf import settings as django_settings
|
||||||
|
from django.shortcuts import redirect
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
|
|
||||||
|
@ -201,3 +202,21 @@ def filter_stream_by_status_type(activities, allowed_types=None):
|
||||||
)
|
)
|
||||||
|
|
||||||
return activities
|
return activities
|
||||||
|
|
||||||
|
|
||||||
|
def maybe_redirect_local_path(request, model):
|
||||||
|
"""
|
||||||
|
if the request had an invalid path, return a permanent redirect response to the
|
||||||
|
correct one, including a slug if any.
|
||||||
|
if path is valid, returns False.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# don't redirect empty path for unit tests which currently have this
|
||||||
|
if request.path in ("/", model.local_path):
|
||||||
|
return False
|
||||||
|
|
||||||
|
new_path = model.local_path
|
||||||
|
if len(request.GET) > 0:
|
||||||
|
new_path = f"{model.local_path}?{request.GET.urlencode()}"
|
||||||
|
|
||||||
|
return redirect(new_path, permanent=True)
|
||||||
|
|
|
@ -18,21 +18,27 @@ from django.views.decorators.http import require_POST
|
||||||
from bookwyrm import book_search, forms, models
|
from bookwyrm import book_search, forms, models
|
||||||
from bookwyrm.activitypub import ActivitypubResponse
|
from bookwyrm.activitypub import ActivitypubResponse
|
||||||
from bookwyrm.settings import PAGE_LENGTH
|
from bookwyrm.settings import PAGE_LENGTH
|
||||||
from bookwyrm.views.helpers import is_api_request
|
from bookwyrm.views.helpers import is_api_request, maybe_redirect_local_path
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
class List(View):
|
class List(View):
|
||||||
"""book list page"""
|
"""book list page"""
|
||||||
|
|
||||||
def get(self, request, list_id, add_failed=False, add_succeeded=False):
|
def get(self, request, list_id, **kwargs):
|
||||||
"""display a book list"""
|
"""display a book list"""
|
||||||
|
add_failed = kwargs.get("add_failed", False)
|
||||||
|
add_succeeded = kwargs.get("add_succeeded", False)
|
||||||
|
|
||||||
book_list = get_object_or_404(models.List, id=list_id)
|
book_list = get_object_or_404(models.List, id=list_id)
|
||||||
book_list.raise_visible_to_user(request.user)
|
book_list.raise_visible_to_user(request.user)
|
||||||
|
|
||||||
if is_api_request(request):
|
if is_api_request(request):
|
||||||
return ActivitypubResponse(book_list.to_activity(**request.GET))
|
return ActivitypubResponse(book_list.to_activity(**request.GET))
|
||||||
|
|
||||||
|
if r := maybe_redirect_local_path(request, book_list):
|
||||||
|
return r
|
||||||
|
|
||||||
query = request.GET.get("q")
|
query = request.GET.get("q")
|
||||||
suggestions = None
|
suggestions = None
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,8 @@ class Search(View):
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
"""that search bar up top"""
|
"""that search bar up top"""
|
||||||
query = request.GET.get("q")
|
query = request.GET.get("q")
|
||||||
|
# check if query is isbn
|
||||||
|
query = isbn_check(query)
|
||||||
min_confidence = request.GET.get("min_confidence", 0)
|
min_confidence = request.GET.get("min_confidence", 0)
|
||||||
search_type = request.GET.get("type")
|
search_type = request.GET.get("type")
|
||||||
search_remote = (
|
search_remote = (
|
||||||
|
@ -123,3 +125,35 @@ def list_search(query, viewer, *_):
|
||||||
)
|
)
|
||||||
.order_by("-similarity")
|
.order_by("-similarity")
|
||||||
), None
|
), None
|
||||||
|
|
||||||
|
|
||||||
|
def isbn_check(query):
|
||||||
|
"""isbn10 or isbn13 check, if so remove separators"""
|
||||||
|
if query:
|
||||||
|
su_num = re.sub(r"(?<=\d)\D(?=\d|[xX])", "", query)
|
||||||
|
if len(su_num) == 13 and su_num.isdecimal():
|
||||||
|
# Multiply every other digit by 3
|
||||||
|
# Add these numbers and the other digits
|
||||||
|
product = sum(int(ch) for ch in su_num[::2]) + sum(
|
||||||
|
int(ch) * 3 for ch in su_num[1::2]
|
||||||
|
)
|
||||||
|
if product % 10 == 0:
|
||||||
|
return su_num
|
||||||
|
elif (
|
||||||
|
len(su_num) == 10
|
||||||
|
and su_num[:-1].isdecimal()
|
||||||
|
and (su_num[-1].isdecimal() or su_num[-1].lower() == "x")
|
||||||
|
):
|
||||||
|
product = 0
|
||||||
|
# Iterate through code_string
|
||||||
|
for i in range(9):
|
||||||
|
# for each character, multiply by a different decreasing number: 10 - x
|
||||||
|
product = product + int(su_num[i]) * (10 - i)
|
||||||
|
# Handle last character
|
||||||
|
if su_num[9].lower() == "x":
|
||||||
|
product += 10
|
||||||
|
else:
|
||||||
|
product += int(su_num[9])
|
||||||
|
if product % 11 == 0:
|
||||||
|
return su_num
|
||||||
|
return query
|
||||||
|
|
|
@ -3,6 +3,7 @@ version: '3'
|
||||||
services:
|
services:
|
||||||
nginx:
|
nginx:
|
||||||
image: nginx:latest
|
image: nginx:latest
|
||||||
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
- 443:443
|
- 443:443
|
||||||
|
|
Loading…
Reference in a new issue