mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-26 19:41:11 +00:00
Merge pull request #2007 from viviicat/url-names
Add names of books/lists/authors/etc as slugs, redirect to slugified version of the page
This commit is contained in:
commit
241169650d
15 changed files with 106 additions and 23 deletions
|
@ -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 %}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue