Merge branch 'main' into notifications
This commit is contained in:
commit
a718a168a3
168 changed files with 4564 additions and 3141 deletions
|
@ -1,5 +1,7 @@
|
|||
""" instance overview """
|
||||
from datetime import timedelta
|
||||
import re
|
||||
|
||||
from dateutil.parser import parse
|
||||
from packaging import version
|
||||
|
||||
|
@ -13,6 +15,7 @@ from django.views import View
|
|||
from bookwyrm import models, settings
|
||||
from bookwyrm.connectors.abstract_connector import get_data
|
||||
from bookwyrm.connectors.connector_manager import ConnectorException
|
||||
from bookwyrm.utils import regex
|
||||
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
|
@ -26,90 +29,18 @@ class Dashboard(View):
|
|||
|
||||
def get(self, request):
|
||||
"""list of users"""
|
||||
interval = int(request.GET.get("days", 1))
|
||||
now = timezone.now()
|
||||
start = request.GET.get("start")
|
||||
if start:
|
||||
start = timezone.make_aware(parse(start))
|
||||
else:
|
||||
start = now - timedelta(days=6 * interval)
|
||||
data = get_charts_and_stats(request)
|
||||
|
||||
end = request.GET.get("end")
|
||||
end = timezone.make_aware(parse(end)) if end else now
|
||||
start = start.replace(hour=0, minute=0, second=0)
|
||||
# Make sure email looks properly configured
|
||||
email_config_error = re.findall(
|
||||
r"[\s\@]", settings.EMAIL_SENDER_DOMAIN
|
||||
) or not re.match(regex.DOMAIN, settings.EMAIL_SENDER_DOMAIN)
|
||||
|
||||
user_queryset = models.User.objects.filter(local=True)
|
||||
user_chart = Chart(
|
||||
queryset=user_queryset,
|
||||
queries={
|
||||
"total": lambda q, s, e: q.filter(
|
||||
Q(is_active=True) | Q(deactivation_date__gt=e),
|
||||
created_date__lte=e,
|
||||
).count(),
|
||||
"active": lambda q, s, e: q.filter(
|
||||
Q(is_active=True) | Q(deactivation_date__gt=e),
|
||||
created_date__lte=e,
|
||||
)
|
||||
.filter(
|
||||
last_active_date__gt=e - timedelta(days=31),
|
||||
)
|
||||
.count(),
|
||||
},
|
||||
)
|
||||
|
||||
status_queryset = models.Status.objects.filter(user__local=True, deleted=False)
|
||||
status_chart = Chart(
|
||||
queryset=status_queryset,
|
||||
queries={
|
||||
"total": lambda q, s, e: q.filter(
|
||||
created_date__gt=s,
|
||||
created_date__lte=e,
|
||||
).count()
|
||||
},
|
||||
)
|
||||
|
||||
register_chart = Chart(
|
||||
queryset=user_queryset,
|
||||
queries={
|
||||
"total": lambda q, s, e: q.filter(
|
||||
created_date__gt=s,
|
||||
created_date__lte=e,
|
||||
).count()
|
||||
},
|
||||
)
|
||||
|
||||
works_chart = Chart(
|
||||
queryset=models.Work.objects,
|
||||
queries={
|
||||
"total": lambda q, s, e: q.filter(
|
||||
created_date__gt=s,
|
||||
created_date__lte=e,
|
||||
).count()
|
||||
},
|
||||
)
|
||||
|
||||
data = {
|
||||
"start": start.strftime("%Y-%m-%d"),
|
||||
"end": end.strftime("%Y-%m-%d"),
|
||||
"interval": interval,
|
||||
"users": user_queryset.filter(is_active=True).count(),
|
||||
"active_users": user_queryset.filter(
|
||||
is_active=True, last_active_date__gte=now - timedelta(days=31)
|
||||
).count(),
|
||||
"statuses": status_queryset.count(),
|
||||
"works": models.Work.objects.count(),
|
||||
"reports": models.Report.objects.filter(resolved=False).count(),
|
||||
"pending_domains": models.LinkDomain.objects.filter(
|
||||
status="pending"
|
||||
).count(),
|
||||
"invite_requests": models.InviteRequest.objects.filter(
|
||||
ignored=False, invite_sent=False
|
||||
).count(),
|
||||
"user_stats": user_chart.get_chart(start, end, interval),
|
||||
"status_stats": status_chart.get_chart(start, end, interval),
|
||||
"register_stats": register_chart.get_chart(start, end, interval),
|
||||
"works_stats": works_chart.get_chart(start, end, interval),
|
||||
}
|
||||
data["email_config_error"] = email_config_error
|
||||
# pylint: disable=line-too-long
|
||||
data[
|
||||
"email_sender"
|
||||
] = f"{settings.EMAIL_SENDER_NAME}@{settings.EMAIL_SENDER_DOMAIN}"
|
||||
|
||||
# check version
|
||||
try:
|
||||
|
@ -126,6 +57,91 @@ class Dashboard(View):
|
|||
return TemplateResponse(request, "settings/dashboard/dashboard.html", data)
|
||||
|
||||
|
||||
def get_charts_and_stats(request):
|
||||
"""Defines the dashbaord charts"""
|
||||
interval = int(request.GET.get("days", 1))
|
||||
now = timezone.now()
|
||||
start = request.GET.get("start")
|
||||
if start:
|
||||
start = timezone.make_aware(parse(start))
|
||||
else:
|
||||
start = now - timedelta(days=6 * interval)
|
||||
|
||||
end = request.GET.get("end")
|
||||
end = timezone.make_aware(parse(end)) if end else now
|
||||
start = start.replace(hour=0, minute=0, second=0)
|
||||
|
||||
user_queryset = models.User.objects.filter(local=True)
|
||||
user_chart = Chart(
|
||||
queryset=user_queryset,
|
||||
queries={
|
||||
"total": lambda q, s, e: q.filter(
|
||||
Q(is_active=True) | Q(deactivation_date__gt=e),
|
||||
created_date__lte=e,
|
||||
).count(),
|
||||
"active": lambda q, s, e: q.filter(
|
||||
Q(is_active=True) | Q(deactivation_date__gt=e),
|
||||
created_date__lte=e,
|
||||
)
|
||||
.filter(
|
||||
last_active_date__gt=e - timedelta(days=31),
|
||||
)
|
||||
.count(),
|
||||
},
|
||||
)
|
||||
|
||||
status_queryset = models.Status.objects.filter(user__local=True, deleted=False)
|
||||
status_chart = Chart(
|
||||
queryset=status_queryset,
|
||||
queries={
|
||||
"total": lambda q, s, e: q.filter(
|
||||
created_date__gt=s,
|
||||
created_date__lte=e,
|
||||
).count()
|
||||
},
|
||||
)
|
||||
|
||||
register_chart = Chart(
|
||||
queryset=user_queryset,
|
||||
queries={
|
||||
"total": lambda q, s, e: q.filter(
|
||||
created_date__gt=s,
|
||||
created_date__lte=e,
|
||||
).count()
|
||||
},
|
||||
)
|
||||
|
||||
works_chart = Chart(
|
||||
queryset=models.Work.objects,
|
||||
queries={
|
||||
"total": lambda q, s, e: q.filter(
|
||||
created_date__gt=s,
|
||||
created_date__lte=e,
|
||||
).count()
|
||||
},
|
||||
)
|
||||
return {
|
||||
"start": start.strftime("%Y-%m-%d"),
|
||||
"end": end.strftime("%Y-%m-%d"),
|
||||
"interval": interval,
|
||||
"users": user_queryset.filter(is_active=True).count(),
|
||||
"active_users": user_queryset.filter(
|
||||
is_active=True, last_active_date__gte=now - timedelta(days=31)
|
||||
).count(),
|
||||
"statuses": status_queryset.count(),
|
||||
"works": models.Work.objects.count(),
|
||||
"reports": models.Report.objects.filter(resolved=False).count(),
|
||||
"pending_domains": models.LinkDomain.objects.filter(status="pending").count(),
|
||||
"invite_requests": models.InviteRequest.objects.filter(
|
||||
ignored=False, invite__isnull=True
|
||||
).count(),
|
||||
"user_stats": user_chart.get_chart(start, end, interval),
|
||||
"status_stats": status_chart.get_chart(start, end, interval),
|
||||
"register_stats": register_chart.get_chart(start, end, interval),
|
||||
"works_stats": works_chart.get_chart(start, end, interval),
|
||||
}
|
||||
|
||||
|
||||
class Chart:
|
||||
"""Data for a chart"""
|
||||
|
||||
|
|
|
@ -29,6 +29,8 @@ class Federation(View):
|
|||
filters = {}
|
||||
if software := request.GET.get("application_type"):
|
||||
filters["application_type"] = software
|
||||
if server := request.GET.get("server"):
|
||||
filters["server_name"] = server
|
||||
|
||||
servers = models.FederatedServer.objects.filter(status=status, **filters)
|
||||
|
||||
|
@ -60,7 +62,9 @@ class Federation(View):
|
|||
"sort": sort,
|
||||
"software_options": models.FederatedServer.objects.values_list(
|
||||
"application_type", flat=True
|
||||
).distinct(),
|
||||
)
|
||||
.distinct()
|
||||
.order_by("application_type"),
|
||||
"form": forms.ServerForm(),
|
||||
}
|
||||
return TemplateResponse(request, "settings/federation/instance_list.html", data)
|
||||
|
|
|
@ -22,19 +22,16 @@ class UserAdminList(View):
|
|||
def get(self, request, status="local"):
|
||||
"""list of users"""
|
||||
filters = {}
|
||||
server = request.GET.get("server")
|
||||
if server:
|
||||
if server := request.GET.get("server"):
|
||||
server = models.FederatedServer.objects.filter(server_name=server).first()
|
||||
filters["federated_server"] = server
|
||||
filters["federated_server__isnull"] = False
|
||||
username = request.GET.get("username")
|
||||
if username:
|
||||
if username := request.GET.get("username"):
|
||||
filters["username__icontains"] = username
|
||||
scope = request.GET.get("scope")
|
||||
if scope and scope == "local":
|
||||
filters["local"] = True
|
||||
email = request.GET.get("email")
|
||||
if email:
|
||||
if email := request.GET.get("email"):
|
||||
filters["email__endswith"] = email
|
||||
|
||||
filters["local"] = status == "local"
|
||||
|
|
|
@ -11,20 +11,24 @@ from bookwyrm import forms, models
|
|||
from bookwyrm.activitypub import ActivitypubResponse
|
||||
from bookwyrm.connectors import connector_manager
|
||||
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
|
||||
class Author(View):
|
||||
"""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"""
|
||||
author = get_object_or_404(models.Author, id=author_id)
|
||||
|
||||
if is_api_request(request):
|
||||
return ActivitypubResponse(author.to_activity())
|
||||
|
||||
if redirect_local_path := maybe_redirect_local_path(request, author):
|
||||
return redirect_local_path
|
||||
|
||||
books = (
|
||||
models.Work.objects.filter(editions__authors=author)
|
||||
.order_by("created_date")
|
||||
|
|
|
@ -15,14 +15,14 @@ from bookwyrm.activitypub import ActivitypubResponse
|
|||
from bookwyrm.connectors import connector_manager, ConnectorException
|
||||
from bookwyrm.connectors.abstract_connector import get_image
|
||||
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
|
||||
class Book(View):
|
||||
"""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"""
|
||||
if is_api_request(request):
|
||||
book = get_object_or_404(
|
||||
|
@ -30,7 +30,11 @@ class Book(View):
|
|||
)
|
||||
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
|
||||
# table, so they never have clashing IDs
|
||||
|
@ -46,6 +50,11 @@ class Book(View):
|
|||
if not book or not book.parent_work:
|
||||
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
|
||||
reviews = models.Review.privacy_filter(request.user).filter(
|
||||
book__parent_work__editions=book
|
||||
|
@ -80,7 +89,7 @@ class Book(View):
|
|||
else None,
|
||||
"rating": reviews.aggregate(Avg("rating"))["rating__avg"],
|
||||
"lists": lists,
|
||||
"update_error": update_error,
|
||||
"update_error": kwargs.get("update_error", False),
|
||||
}
|
||||
|
||||
if request.user.is_authenticated:
|
||||
|
|
|
@ -115,6 +115,7 @@ class CreateBook(View):
|
|||
|
||||
# go to confirm mode
|
||||
if not parent_work_id or data.get("add_author"):
|
||||
data["confirm_mode"] = True
|
||||
return TemplateResponse(request, "book/edit/edit_book.html", data)
|
||||
|
||||
with transaction.atomic():
|
||||
|
@ -189,7 +190,7 @@ def add_authors(request, data):
|
|||
"existing_isnis": exists,
|
||||
}
|
||||
)
|
||||
return data
|
||||
return data
|
||||
|
||||
|
||||
@require_POST
|
||||
|
|
|
@ -15,7 +15,7 @@ from bookwyrm.activitypub import ActivitypubResponse
|
|||
from bookwyrm.settings import PAGE_LENGTH, STREAMS
|
||||
from bookwyrm.suggested_users import suggested_users
|
||||
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
|
||||
|
||||
|
||||
|
@ -113,7 +113,8 @@ class DirectMessage(View):
|
|||
class Status(View):
|
||||
"""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)"""
|
||||
user = get_user_from_username(request.user, username)
|
||||
status = get_object_or_404(
|
||||
|
@ -130,6 +131,9 @@ class Status(View):
|
|||
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 = (
|
||||
models.Status.privacy_filter(request.user)
|
||||
.filter(thread_id=status.thread_id)
|
||||
|
|
|
@ -14,17 +14,22 @@ from django.db.models.functions import Greatest
|
|||
|
||||
from bookwyrm import forms, models
|
||||
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
|
||||
class Group(View):
|
||||
"""group page"""
|
||||
|
||||
def get(self, request, group_id):
|
||||
# pylint: disable=unused-argument
|
||||
def get(self, request, group_id, slug=None):
|
||||
"""display a group"""
|
||||
|
||||
group = get_object_or_404(models.Group, id=group_id)
|
||||
group.raise_visible_to_user(request.user)
|
||||
|
||||
if redirect_local_path := maybe_redirect_local_path(request, group):
|
||||
return redirect_local_path
|
||||
|
||||
lists = (
|
||||
models.List.privacy_filter(request.user)
|
||||
.filter(group=group)
|
||||
|
@ -80,7 +85,8 @@ class Group(View):
|
|||
class UserGroups(View):
|
||||
"""a user's groups page"""
|
||||
|
||||
def get(self, request, username):
|
||||
# pylint: disable=unused-argument
|
||||
def get(self, request, username, slug=None):
|
||||
"""display a group"""
|
||||
user = get_user_from_username(request.user, username)
|
||||
groups = (
|
||||
|
|
|
@ -8,6 +8,7 @@ from dateutil.parser import ParserError
|
|||
from requests import HTTPError
|
||||
from django.db.models import Q
|
||||
from django.conf import settings as django_settings
|
||||
from django.shortcuts import redirect
|
||||
from django.http import Http404
|
||||
from django.utils import translation
|
||||
|
||||
|
@ -137,6 +138,7 @@ def handle_reading_status(user, shelf, book, privacy):
|
|||
"to-read": "wants to read",
|
||||
"reading": "started reading",
|
||||
"read": "finished reading",
|
||||
"stopped-reading": "stopped reading",
|
||||
}[shelf.identifier]
|
||||
except KeyError:
|
||||
# it's a non-standard shelf, don't worry about it
|
||||
|
@ -201,3 +203,21 @@ def filter_stream_by_status_type(activities, allowed_types=None):
|
|||
)
|
||||
|
||||
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)
|
||||
|
|
|
@ -11,6 +11,7 @@ from django.views import View
|
|||
|
||||
from bookwyrm import forms, models
|
||||
from bookwyrm.importers import (
|
||||
CalibreImporter,
|
||||
LibrarythingImporter,
|
||||
GoodreadsImporter,
|
||||
StorygraphImporter,
|
||||
|
@ -52,6 +53,8 @@ class Import(View):
|
|||
importer = StorygraphImporter()
|
||||
elif source == "OpenLibrary":
|
||||
importer = OpenLibraryImporter()
|
||||
elif source == "Calibre":
|
||||
importer = CalibreImporter()
|
||||
else:
|
||||
# Default : Goodreads
|
||||
importer = GoodreadsImporter()
|
||||
|
|
|
@ -91,9 +91,12 @@ class ConfirmEmailCode(View):
|
|||
def get(self, request, code): # pylint: disable=unused-argument
|
||||
"""you got the code! good work"""
|
||||
settings = models.SiteSettings.get()
|
||||
if request.user.is_authenticated or not settings.require_confirm_email:
|
||||
if request.user.is_authenticated:
|
||||
return redirect("/")
|
||||
|
||||
if not settings.require_confirm_email:
|
||||
return redirect("login")
|
||||
|
||||
# look up the user associated with this code
|
||||
try:
|
||||
user = models.User.objects.get(confirmation_code=code)
|
||||
|
|
|
@ -18,21 +18,27 @@ from django.views.decorators.http import require_POST
|
|||
from bookwyrm import book_search, forms, models
|
||||
from bookwyrm.activitypub import ActivitypubResponse
|
||||
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
|
||||
class List(View):
|
||||
"""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"""
|
||||
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.raise_visible_to_user(request.user)
|
||||
|
||||
if is_api_request(request):
|
||||
return ActivitypubResponse(book_list.to_activity(**request.GET))
|
||||
|
||||
if r := maybe_redirect_local_path(request, book_list):
|
||||
return r
|
||||
|
||||
query = request.GET.get("q")
|
||||
suggestions = None
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
""" the good stuff! the books! """
|
||||
import logging
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.cache import cache
|
||||
from django.db import transaction
|
||||
|
@ -15,6 +16,8 @@ from .status import CreateStatus
|
|||
from .helpers import get_edition, handle_reading_status, is_api_request
|
||||
from .helpers import load_date_in_user_tz_as_utc
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
# pylint: disable=too-many-return-statements
|
||||
|
@ -29,20 +32,24 @@ class ReadingStatus(View):
|
|||
"want": "want.html",
|
||||
"start": "start.html",
|
||||
"finish": "finish.html",
|
||||
"stop": "stop.html",
|
||||
}.get(status)
|
||||
if not template:
|
||||
return HttpResponseNotFound()
|
||||
# redirect if we're already on this shelf
|
||||
return TemplateResponse(request, f"reading_progress/{template}", {"book": book})
|
||||
|
||||
@transaction.atomic
|
||||
def post(self, request, status, book_id):
|
||||
"""Change the state of a book by shelving it and adding reading dates"""
|
||||
identifier = {
|
||||
"want": models.Shelf.TO_READ,
|
||||
"start": models.Shelf.READING,
|
||||
"finish": models.Shelf.READ_FINISHED,
|
||||
"stop": models.Shelf.STOPPED_READING,
|
||||
}.get(status)
|
||||
if not identifier:
|
||||
logger.exception("Invalid reading status type: %s", status)
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
# invalidate related caches
|
||||
|
@ -85,6 +92,7 @@ class ReadingStatus(View):
|
|||
desired_shelf.identifier,
|
||||
start_date=request.POST.get("start_date"),
|
||||
finish_date=request.POST.get("finish_date"),
|
||||
stopped_date=request.POST.get("stopped_date"),
|
||||
)
|
||||
|
||||
# post about it (if you want)
|
||||
|
@ -153,8 +161,9 @@ class ReadThrough(View):
|
|||
|
||||
|
||||
@transaction.atomic
|
||||
# pylint: disable=too-many-arguments
|
||||
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"""
|
||||
# there *should* only be one of current active readthrough, but it's a list
|
||||
|
@ -176,8 +185,9 @@ def update_readthrough_on_shelve(
|
|||
)
|
||||
# santiize and set dates
|
||||
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.stopped_date = load_date_in_user_tz_as_utc(stopped_date, user)
|
||||
|
||||
active_readthrough.save()
|
||||
|
||||
|
|
|
@ -24,6 +24,8 @@ class Search(View):
|
|||
def get(self, request):
|
||||
"""that search bar up top"""
|
||||
query = request.GET.get("q")
|
||||
# check if query is isbn
|
||||
query = isbn_check(query)
|
||||
min_confidence = request.GET.get("min_confidence", 0)
|
||||
search_type = request.GET.get("type")
|
||||
search_remote = (
|
||||
|
@ -123,3 +125,35 @@ def list_search(query, viewer, *_):
|
|||
)
|
||||
.order_by("-similarity")
|
||||
), 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
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
""" what are we here for if not for posting """
|
||||
import re
|
||||
import logging
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
@ -15,12 +16,13 @@ from django.views.decorators.http import require_POST
|
|||
|
||||
from markdown import markdown
|
||||
from bookwyrm import forms, models
|
||||
from bookwyrm.sanitize_html import InputHtmlParser
|
||||
from bookwyrm.settings import DOMAIN
|
||||
from bookwyrm.utils import regex
|
||||
from bookwyrm.utils import regex, sanitizer
|
||||
from .helpers import handle_remote_webfinger, is_api_request
|
||||
from .helpers import load_date_in_user_tz_as_utc
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
|
@ -72,11 +74,14 @@ class CreateStatus(View):
|
|||
form = getattr(forms, f"{status_type}Form")(
|
||||
request.POST, instance=existing_status
|
||||
)
|
||||
except AttributeError:
|
||||
except AttributeError as err:
|
||||
logger.exception(err)
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
if not form.is_valid():
|
||||
if is_api_request(request):
|
||||
return HttpResponse(status=500)
|
||||
logger.exception(form.errors)
|
||||
return HttpResponseBadRequest()
|
||||
return redirect(request.headers.get("Referer", "/"))
|
||||
|
||||
status = form.save(commit=False)
|
||||
|
@ -262,6 +267,4 @@ def to_markdown(content):
|
|||
content = format_links(content)
|
||||
content = markdown(content)
|
||||
# sanitize resulting html
|
||||
sanitizer = InputHtmlParser()
|
||||
sanitizer.feed(content)
|
||||
return sanitizer.get_output()
|
||||
return sanitizer.clean(content)
|
||||
|
|
|
@ -106,7 +106,7 @@ class Followers(View):
|
|||
if is_api_request(request):
|
||||
return ActivitypubResponse(user.to_followers_activity(**request.GET))
|
||||
|
||||
if user.hide_follows:
|
||||
if user.hide_follows and user != request.user:
|
||||
raise PermissionDenied()
|
||||
|
||||
followers = annotate_if_follows(request.user, user.followers)
|
||||
|
@ -129,7 +129,7 @@ class Following(View):
|
|||
if is_api_request(request):
|
||||
return ActivitypubResponse(user.to_following_activity(**request.GET))
|
||||
|
||||
if user.hide_follows:
|
||||
if user.hide_follows and user != request.user:
|
||||
raise PermissionDenied()
|
||||
|
||||
following = annotate_if_follows(request.user, user.following)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue