Merge branch 'main' into switch_edition_invalidate_active_shelves
This commit is contained in:
commit
8e088a6d53
99 changed files with 8734 additions and 556 deletions
|
@ -5,6 +5,7 @@ from .admin.announcements import EditAnnouncement, delete_announcement
|
|||
from .admin.automod import AutoMod, automod_delete, run_automod
|
||||
from .admin.automod import schedule_automod_task, unschedule_automod_task
|
||||
from .admin.celery_status import CeleryStatus, celery_ping
|
||||
from .admin.schedule import ScheduledTasks
|
||||
from .admin.dashboard import Dashboard
|
||||
from .admin.federation import Federation, FederatedServer
|
||||
from .admin.federation import AddFederatedServer, ImportServerBlocklist
|
||||
|
@ -18,6 +19,8 @@ from .admin.imports import (
|
|||
set_import_size_limit,
|
||||
set_user_import_completed,
|
||||
set_user_import_limit,
|
||||
enable_user_exports,
|
||||
disable_user_exports,
|
||||
)
|
||||
from .admin.ip_blocklist import IPBlocklist
|
||||
from .admin.invite import ManageInvites, Invite, InviteRequest
|
||||
|
|
|
@ -6,7 +6,7 @@ from django.template.response import TemplateResponse
|
|||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
from django.views.decorators.http import require_POST
|
||||
from django_celery_beat.models import PeriodicTask
|
||||
from django_celery_beat.models import PeriodicTask, IntervalSchedule
|
||||
|
||||
from bookwyrm import forms, models
|
||||
|
||||
|
@ -54,7 +54,7 @@ def schedule_automod_task(request):
|
|||
return TemplateResponse(request, "settings/automod/rules.html", data)
|
||||
|
||||
with transaction.atomic():
|
||||
schedule = form.save(request)
|
||||
schedule, _ = IntervalSchedule.objects.get_or_create(**form.cleaned_data)
|
||||
PeriodicTask.objects.get_or_create(
|
||||
interval=schedule,
|
||||
name="automod-task",
|
||||
|
|
|
@ -6,16 +6,18 @@ from dateutil.parser import parse
|
|||
from packaging import version
|
||||
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
from django_celery_beat.models import PeriodicTask, IntervalSchedule
|
||||
|
||||
from csp.decorators import csp_update
|
||||
|
||||
from bookwyrm import models, settings
|
||||
from bookwyrm.connectors.abstract_connector import get_data
|
||||
from bookwyrm import forms, models, settings
|
||||
from bookwyrm.utils import regex
|
||||
|
||||
|
||||
|
@ -59,21 +61,36 @@ class Dashboard(View):
|
|||
== site._meta.get_field("privacy_policy").get_default()
|
||||
)
|
||||
|
||||
# check version
|
||||
if site.available_version and version.parse(
|
||||
site.available_version
|
||||
) > version.parse(settings.VERSION):
|
||||
data["current_version"] = settings.VERSION
|
||||
data["available_version"] = site.available_version
|
||||
|
||||
try:
|
||||
release = get_data(settings.RELEASE_API, timeout=3)
|
||||
available_version = release.get("tag_name", None)
|
||||
if available_version and version.parse(available_version) > version.parse(
|
||||
settings.VERSION
|
||||
):
|
||||
data["current_version"] = settings.VERSION
|
||||
data["available_version"] = available_version
|
||||
except: # pylint: disable= bare-except
|
||||
pass
|
||||
if not PeriodicTask.objects.filter(name="check-for-updates").exists():
|
||||
data["schedule_form"] = forms.IntervalScheduleForm(
|
||||
{"every": 1, "period": "days"}
|
||||
)
|
||||
|
||||
return TemplateResponse(request, "settings/dashboard/dashboard.html", data)
|
||||
|
||||
def post(self, request):
|
||||
"""Create a schedule task to check for updates"""
|
||||
schedule_form = forms.IntervalScheduleForm(request.POST)
|
||||
if not schedule_form.is_valid():
|
||||
raise schedule_form.ValidationError(schedule_form.errors)
|
||||
|
||||
with transaction.atomic():
|
||||
schedule, _ = IntervalSchedule.objects.get_or_create(
|
||||
**schedule_form.cleaned_data
|
||||
)
|
||||
PeriodicTask.objects.get_or_create(
|
||||
interval=schedule,
|
||||
name="check-for-updates",
|
||||
task="bookwyrm.models.site.check_for_updates_task",
|
||||
)
|
||||
return redirect("settings-dashboard")
|
||||
|
||||
|
||||
def get_charts_and_stats(request):
|
||||
"""Defines the dashboard charts"""
|
||||
|
|
|
@ -9,7 +9,7 @@ from django.views.decorators.http import require_POST
|
|||
|
||||
from bookwyrm import models
|
||||
from bookwyrm.views.helpers import redirect_to_referer
|
||||
from bookwyrm.settings import PAGE_LENGTH
|
||||
from bookwyrm.settings import PAGE_LENGTH, USE_S3
|
||||
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
|
@ -59,6 +59,7 @@ class ImportList(View):
|
|||
"import_size_limit": site_settings.import_size_limit,
|
||||
"import_limit_reset": site_settings.import_limit_reset,
|
||||
"user_import_time_limit": site_settings.user_import_time_limit,
|
||||
"use_s3": USE_S3,
|
||||
}
|
||||
return TemplateResponse(request, "settings/imports/imports.html", data)
|
||||
|
||||
|
@ -126,3 +127,25 @@ def set_user_import_limit(request):
|
|||
site.user_import_time_limit = int(request.POST.get("limit"))
|
||||
site.save(update_fields=["user_import_time_limit"])
|
||||
return redirect("settings-imports")
|
||||
|
||||
|
||||
@require_POST
|
||||
@permission_required("bookwyrm.edit_instance_settings", raise_exception=True)
|
||||
# pylint: disable=unused-argument
|
||||
def enable_user_exports(request):
|
||||
"""Allow users to export account data"""
|
||||
site = models.SiteSettings.objects.get()
|
||||
site.user_exports_enabled = True
|
||||
site.save(update_fields=["user_exports_enabled"])
|
||||
return redirect("settings-imports")
|
||||
|
||||
|
||||
@require_POST
|
||||
@permission_required("bookwyrm.edit_instance_settings", raise_exception=True)
|
||||
# pylint: disable=unused-argument
|
||||
def disable_user_exports(request):
|
||||
"""Don't allow users to export account data"""
|
||||
site = models.SiteSettings.objects.get()
|
||||
site.user_exports_enabled = False
|
||||
site.save(update_fields=["user_exports_enabled"])
|
||||
return redirect("settings-imports")
|
||||
|
|
31
bookwyrm/views/admin/schedule.py
Normal file
31
bookwyrm/views/admin/schedule.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
""" Scheduled celery tasks """
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.shortcuts import redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
from django_celery_beat.models import PeriodicTask, IntervalSchedule
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
@method_decorator(
|
||||
permission_required("bookwyrm.edit_instance_settings", raise_exception=True),
|
||||
name="dispatch",
|
||||
)
|
||||
# pylint: disable=no-self-use
|
||||
class ScheduledTasks(View):
|
||||
"""Manage automated flagging"""
|
||||
|
||||
def get(self, request):
|
||||
"""view schedules"""
|
||||
data = {}
|
||||
data["tasks"] = PeriodicTask.objects.all()
|
||||
data["schedules"] = IntervalSchedule.objects.all()
|
||||
return TemplateResponse(request, "settings/schedules.html", data)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def post(self, request, task_id):
|
||||
"""un-schedule a task"""
|
||||
task = PeriodicTask.objects.get(id=task_id)
|
||||
task.delete()
|
||||
return redirect("settings-schedules")
|
|
@ -94,6 +94,7 @@ def switch_edition(request):
|
|||
user=shelfbook.user,
|
||||
shelf=shelfbook.shelf,
|
||||
book=new_edition,
|
||||
shelved_date=shelfbook.shelved_date,
|
||||
)
|
||||
shelfbook.delete()
|
||||
|
||||
|
@ -110,4 +111,14 @@ def switch_edition(request):
|
|||
for book_id in new_edition.parent_work.editions.values_list("id", flat=True)
|
||||
]
|
||||
)
|
||||
|
||||
reviews = models.Review.objects.filter(
|
||||
book__parent_work=new_edition.parent_work, user=request.user
|
||||
)
|
||||
for review in reviews.all():
|
||||
# because ratings are a subclass of reviews,
|
||||
# this will pick up both ratings and reviews
|
||||
review.book = new_edition
|
||||
review.save()
|
||||
|
||||
return redirect(f"/book/{new_edition.id}")
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
""" non-interactive pages """
|
||||
from datetime import date
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.paginator import Paginator
|
||||
from django.db.models import Q
|
||||
|
@ -52,6 +53,19 @@ class Feed(View):
|
|||
|
||||
suggestions = suggested_users.get_suggestions(request.user)
|
||||
|
||||
cutoff = (
|
||||
date(get_annual_summary_year(), 12, 31)
|
||||
if get_annual_summary_year()
|
||||
else None
|
||||
)
|
||||
readthroughs = (
|
||||
models.ReadThrough.objects.filter(
|
||||
user=request.user, finish_date__lte=cutoff
|
||||
)
|
||||
if get_annual_summary_year()
|
||||
else []
|
||||
)
|
||||
|
||||
data = {
|
||||
**feed_page_data(request.user),
|
||||
**{
|
||||
|
@ -66,6 +80,7 @@ class Feed(View):
|
|||
"path": f"/{tab['key']}",
|
||||
"annual_summary_year": get_annual_summary_year(),
|
||||
"has_tour": True,
|
||||
"has_summary_read_throughs": len(readthroughs),
|
||||
},
|
||||
}
|
||||
return TemplateResponse(request, "feed/feed.html", data)
|
||||
|
@ -185,19 +200,15 @@ class Status(View):
|
|||
params=[status.id, visible_thread, visible_thread],
|
||||
)
|
||||
|
||||
preview = None
|
||||
if hasattr(status, "book"):
|
||||
preview = status.book.preview_image
|
||||
elif status.mention_books.exists():
|
||||
preview = status.mention_books.first().preview_image
|
||||
|
||||
data = {
|
||||
**feed_page_data(request.user),
|
||||
**{
|
||||
"status": status,
|
||||
"children": children,
|
||||
"ancestors": ancestors,
|
||||
"preview": preview,
|
||||
"title": status.page_title,
|
||||
"description": status.page_description,
|
||||
"page_image": status.page_image,
|
||||
},
|
||||
}
|
||||
return TemplateResponse(request, "feed/status.html", data)
|
||||
|
|
|
@ -17,6 +17,7 @@ from bookwyrm import models
|
|||
from bookwyrm.models.bookwyrm_export_job import BookwyrmExportJob
|
||||
from bookwyrm.settings import PAGE_LENGTH
|
||||
|
||||
|
||||
# pylint: disable=no-self-use,too-many-locals
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class Export(View):
|
||||
|
@ -54,8 +55,19 @@ class Export(View):
|
|||
fields = (
|
||||
["title", "author_text"]
|
||||
+ deduplication_fields
|
||||
+ ["start_date", "finish_date", "stopped_date"]
|
||||
+ ["rating", "review_name", "review_cw", "review_content"]
|
||||
+ [
|
||||
"start_date",
|
||||
"finish_date",
|
||||
"stopped_date",
|
||||
"rating",
|
||||
"review_name",
|
||||
"review_cw",
|
||||
"review_content",
|
||||
"review_published",
|
||||
"shelf",
|
||||
"shelf_name",
|
||||
"shelf_date",
|
||||
]
|
||||
)
|
||||
writer.writerow(fields)
|
||||
|
||||
|
@ -97,9 +109,27 @@ class Export(View):
|
|||
.first()
|
||||
)
|
||||
if review:
|
||||
book.review_published = (
|
||||
review.published_date.date() if review.published_date else None
|
||||
)
|
||||
book.review_name = review.name
|
||||
book.review_cw = review.content_warning
|
||||
book.review_content = review.raw_content
|
||||
book.review_content = (
|
||||
review.raw_content if review.raw_content else review.content
|
||||
) # GoodReads imported reviews do not have raw_content, but content.
|
||||
|
||||
shelfbook = (
|
||||
models.ShelfBook.objects.filter(user=request.user, book=book)
|
||||
.order_by("-shelved_date", "-created_date", "-updated_date")
|
||||
.last()
|
||||
)
|
||||
if shelfbook:
|
||||
book.shelf = shelfbook.shelf.identifier
|
||||
book.shelf_name = shelfbook.shelf.name
|
||||
book.shelf_date = (
|
||||
shelfbook.shelved_date.date() if shelfbook.shelved_date else None
|
||||
)
|
||||
|
||||
writer.writerow([getattr(book, field, "") or "" for field in fields])
|
||||
|
||||
return HttpResponse(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
""" search views"""
|
||||
|
||||
import re
|
||||
|
||||
from django.contrib.postgres.search import TrigramSimilarity
|
||||
|
@ -39,6 +40,7 @@ class Search(View):
|
|||
|
||||
endpoints = {
|
||||
"book": book_search,
|
||||
"author": author_search,
|
||||
"user": user_search,
|
||||
"list": list_search,
|
||||
}
|
||||
|
@ -90,6 +92,31 @@ def book_search(request):
|
|||
return TemplateResponse(request, "search/book.html", data)
|
||||
|
||||
|
||||
def author_search(request):
|
||||
"""search for an author"""
|
||||
query = request.GET.get("q")
|
||||
query = query.strip()
|
||||
data = {"type": "author", "query": query}
|
||||
|
||||
results = (
|
||||
models.Author.objects.annotate(
|
||||
similarity=TrigramSimilarity("name", query),
|
||||
)
|
||||
.filter(
|
||||
similarity__gt=0.1,
|
||||
)
|
||||
.order_by("-similarity")
|
||||
)
|
||||
|
||||
paginated = Paginator(results, PAGE_LENGTH)
|
||||
page = paginated.get_page(request.GET.get("page"))
|
||||
data["results"] = page
|
||||
data["page_range"] = paginated.get_elided_page_range(
|
||||
page.number, on_each_side=2, on_ends=1
|
||||
)
|
||||
return TemplateResponse(request, "search/author.html", data)
|
||||
|
||||
|
||||
def user_search(request):
|
||||
"""user search: search for a user"""
|
||||
viewer = request.user
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue