Merge branch 'main' into suggestions-redis
This commit is contained in:
@ -6,6 +6,7 @@ from .block import Block, unblock
|
||||
from .books import Book, EditBook, ConfirmEditBook, Editions
|
||||
from .books import upload_cover, add_description, switch_edition, resolve_book
|
||||
from .directory import Directory
|
||||
from .edit_user import EditUser, DeleteUser
|
||||
from .federation import Federation, FederatedServer
|
||||
from .federation import AddFederatedServer, ImportServerBlocklist
|
||||
from .federation import block_server, unblock_server
|
||||
@ -24,8 +25,9 @@ from .landing import About, Home, Discover
|
||||
from .list import Lists, List, Curate, UserLists
|
||||
from .notifications import Notifications
|
||||
from .outbox import Outbox
|
||||
from .reading import edit_readthrough, create_readthrough, delete_readthrough
|
||||
from .reading import start_reading, finish_reading, delete_progressupdate
|
||||
from .reading import edit_readthrough, create_readthrough
|
||||
from .reading import delete_readthrough, delete_progressupdate
|
||||
from .reading import ReadingStatus
|
||||
from .reports import Report, Reports, make_report, resolve_report, suspend_user
|
||||
from .rss_feed import RssFeed
|
||||
from .password import PasswordResetRequest, PasswordReset, ChangePassword
|
||||
@ -36,6 +38,6 @@ from .shelf import shelve, unshelve
|
||||
from .site import Site
|
||||
from .status import CreateStatus, DeleteStatus, DeleteAndRedraft
|
||||
from .updates import get_notification_count, get_unread_status_count
|
||||
from .user import User, EditUser, Followers, Following
|
||||
from .user import User, Followers, Following
|
||||
from .user_admin import UserAdmin, UserAdminList
|
||||
from .wellknown import *
|
||||
|
@ -81,6 +81,7 @@ class Announcement(View):
|
||||
form = forms.AnnouncementForm(request.POST, instance=announcement)
|
||||
if form.is_valid():
|
||||
announcement = form.save()
|
||||
form = forms.AnnouncementForm(instance=announcement)
|
||||
data = {
|
||||
"announcement": announcement,
|
||||
"form": form,
|
||||
|
@ -62,13 +62,16 @@ class Book(View):
|
||||
queryset = queryset.filter(user=request.user)
|
||||
else:
|
||||
queryset = reviews.exclude(Q(content__isnull=True) | Q(content=""))
|
||||
queryset = queryset.select_related("user")
|
||||
paginated = Paginator(queryset, PAGE_LENGTH)
|
||||
|
||||
data = {
|
||||
"book": book,
|
||||
"statuses": paginated.get_page(request.GET.get("page")),
|
||||
"review_count": reviews.count(),
|
||||
"ratings": reviews.filter(Q(content__isnull=True) | Q(content=""))
|
||||
"ratings": reviews.filter(
|
||||
Q(content__isnull=True) | Q(content="")
|
||||
).select_related("user")
|
||||
if not user_statuses
|
||||
else None,
|
||||
"rating": reviews.aggregate(Avg("rating"))["rating__avg"],
|
||||
@ -89,15 +92,15 @@ class Book(View):
|
||||
)
|
||||
data["readthroughs"] = readthroughs
|
||||
|
||||
data["user_shelves"] = models.ShelfBook.objects.filter(
|
||||
data["user_shelfbooks"] = models.ShelfBook.objects.filter(
|
||||
user=request.user, book=book
|
||||
)
|
||||
).select_related("shelf")
|
||||
|
||||
data["other_edition_shelves"] = models.ShelfBook.objects.filter(
|
||||
~Q(book=book),
|
||||
user=request.user,
|
||||
book__parent_work=book.parent_work,
|
||||
)
|
||||
).select_related("shelf", "book")
|
||||
|
||||
data["user_statuses"] = {
|
||||
"review_count": book.review_set.filter(user=request.user).count(),
|
||||
|
113
bookwyrm/views/edit_user.py
Normal file
113
bookwyrm/views/edit_user.py
Normal file
@ -0,0 +1,113 @@
|
||||
""" edit or delete ones own account"""
|
||||
from io import BytesIO
|
||||
from uuid import uuid4
|
||||
from PIL import Image
|
||||
|
||||
from django.contrib.auth import logout
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.files.base import ContentFile
|
||||
from django.shortcuts import redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
|
||||
from bookwyrm import forms, models
|
||||
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class EditUser(View):
|
||||
"""edit user view"""
|
||||
|
||||
def get(self, request):
|
||||
"""edit profile page for a user"""
|
||||
data = {
|
||||
"form": forms.EditUserForm(instance=request.user),
|
||||
"user": request.user,
|
||||
}
|
||||
return TemplateResponse(request, "preferences/edit_user.html", data)
|
||||
|
||||
def post(self, request):
|
||||
"""les get fancy with images"""
|
||||
form = forms.EditUserForm(request.POST, request.FILES, instance=request.user)
|
||||
if not form.is_valid():
|
||||
data = {"form": form, "user": request.user}
|
||||
return TemplateResponse(request, "preferences/edit_user.html", data)
|
||||
|
||||
user = save_user_form(form)
|
||||
|
||||
return redirect(user.local_path)
|
||||
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class DeleteUser(View):
|
||||
"""delete user view"""
|
||||
|
||||
def get(self, request):
|
||||
"""delete page for a user"""
|
||||
data = {
|
||||
"form": forms.DeleteUserForm(),
|
||||
"user": request.user,
|
||||
}
|
||||
return TemplateResponse(request, "preferences/delete_user.html", data)
|
||||
|
||||
def post(self, request):
|
||||
"""les get fancy with images"""
|
||||
form = forms.DeleteUserForm(request.POST, instance=request.user)
|
||||
form.is_valid()
|
||||
# idk why but I couldn't get check_password to work on request.user
|
||||
user = models.User.objects.get(id=request.user.id)
|
||||
if form.is_valid() and user.check_password(form.cleaned_data["password"]):
|
||||
user.deactivation_reason = "self_deletion"
|
||||
user.delete()
|
||||
logout(request)
|
||||
return redirect("/")
|
||||
|
||||
form.errors["password"] = ["Invalid password"]
|
||||
data = {"form": form, "user": request.user}
|
||||
return TemplateResponse(request, "preferences/delete_user.html", data)
|
||||
|
||||
|
||||
def save_user_form(form):
|
||||
"""special handling for the user form"""
|
||||
user = form.save(commit=False)
|
||||
|
||||
if "avatar" in form.files:
|
||||
# crop and resize avatar upload
|
||||
image = Image.open(form.files["avatar"])
|
||||
image = crop_avatar(image)
|
||||
|
||||
# set the name to a hash
|
||||
extension = form.files["avatar"].name.split(".")[-1]
|
||||
filename = "%s.%s" % (uuid4(), extension)
|
||||
user.avatar.save(filename, image, save=False)
|
||||
user.save()
|
||||
return user
|
||||
|
||||
|
||||
def crop_avatar(image):
|
||||
"""reduce the size and make an avatar square"""
|
||||
target_size = 120
|
||||
width, height = image.size
|
||||
thumbnail_scale = (
|
||||
height / (width / target_size)
|
||||
if height > width
|
||||
else width / (height / target_size)
|
||||
)
|
||||
image.thumbnail([thumbnail_scale, thumbnail_scale])
|
||||
width, height = image.size
|
||||
|
||||
width_diff = width - target_size
|
||||
height_diff = height - target_size
|
||||
cropped = image.crop(
|
||||
(
|
||||
int(width_diff / 2),
|
||||
int(height_diff / 2),
|
||||
int(width - (width_diff / 2)),
|
||||
int(height - (height_diff / 2)),
|
||||
)
|
||||
)
|
||||
output = BytesIO()
|
||||
cropped.save(output, format=image.format)
|
||||
return ContentFile(output.getvalue())
|
@ -163,14 +163,15 @@ def get_suggested_books(user, max_books=5):
|
||||
else max_books - book_count
|
||||
)
|
||||
shelf = user.shelf_set.get(identifier=preset)
|
||||
|
||||
shelf_books = shelf.shelfbook_set.order_by("-updated_date")[:limit]
|
||||
if not shelf_books:
|
||||
if not shelf.books.exists():
|
||||
continue
|
||||
|
||||
shelf_preview = {
|
||||
"name": shelf.name,
|
||||
"identifier": shelf.identifier,
|
||||
"books": [s.book for s in shelf_books],
|
||||
"books": shelf.books.order_by("shelfbook").prefetch_related("authors")[
|
||||
:limit
|
||||
],
|
||||
}
|
||||
suggested_books.append(shelf_preview)
|
||||
book_count += len(shelf_preview["books"])
|
||||
|
@ -14,7 +14,7 @@ from django.views import View
|
||||
from bookwyrm import forms, models
|
||||
from bookwyrm.connectors import connector_manager
|
||||
from bookwyrm.suggested_users import suggested_users
|
||||
from .user import save_user_form
|
||||
from .edit_user import save_user_form
|
||||
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
|
@ -13,6 +13,10 @@ from bookwyrm.utils import regex
|
||||
|
||||
def get_user_from_username(viewer, username):
|
||||
"""helper function to resolve a localname or a username to a user"""
|
||||
if viewer.is_authenticated and viewer.localname == username:
|
||||
# that's yourself, fool
|
||||
return viewer
|
||||
|
||||
# raises 404 if the user isn't found
|
||||
try:
|
||||
return models.User.viewer_aware_objects(viewer).get(localname=username)
|
||||
@ -34,7 +38,7 @@ def is_api_request(request):
|
||||
def is_bookwyrm_request(request):
|
||||
"""check if the request is coming from another bookwyrm instance"""
|
||||
user_agent = request.headers.get("User-Agent")
|
||||
if user_agent is None or re.search(regex.bookwyrm_user_agent, user_agent) is None:
|
||||
if user_agent is None or re.search(regex.BOOKWYRM_USER_AGENT, user_agent) is None:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
@ -78,10 +78,15 @@ class ImportStatus(View):
|
||||
|
||||
def get(self, request, job_id):
|
||||
"""status of an import job"""
|
||||
job = models.ImportJob.objects.get(id=job_id)
|
||||
job = get_object_or_404(models.ImportJob, id=job_id)
|
||||
if job.user != request.user:
|
||||
raise PermissionDenied
|
||||
task = app.AsyncResult(job.task_id)
|
||||
|
||||
try:
|
||||
task = app.AsyncResult(job.task_id)
|
||||
except ValueError:
|
||||
task = None
|
||||
|
||||
items = job.items.order_by("index").all()
|
||||
failed_items = [i for i in items if i.fail_reason]
|
||||
items = [i for i in items if not i.fail_reason]
|
||||
|
@ -21,6 +21,7 @@ from bookwyrm.utils import regex
|
||||
class Inbox(View):
|
||||
"""requests sent by outside servers"""
|
||||
|
||||
# pylint: disable=too-many-return-statements
|
||||
def post(self, request, username=None):
|
||||
"""only works as POST request"""
|
||||
# first check if this server is on our shitlist
|
||||
@ -70,7 +71,7 @@ def is_blocked_user_agent(request):
|
||||
user_agent = request.headers.get("User-Agent")
|
||||
if not user_agent:
|
||||
return False
|
||||
url = re.search(r"https?://{:s}/?".format(regex.domain), user_agent)
|
||||
url = re.search(r"https?://{:s}/?".format(regex.DOMAIN), user_agent)
|
||||
if not url:
|
||||
return False
|
||||
url = url.group()
|
||||
|
@ -37,8 +37,12 @@ class ManageInvites(View):
|
||||
PAGE_LENGTH,
|
||||
)
|
||||
|
||||
page = paginated.get_page(request.GET.get("page"))
|
||||
data = {
|
||||
"invites": paginated.get_page(request.GET.get("page")),
|
||||
"invites": page,
|
||||
"page_range": paginated.get_elided_page_range(
|
||||
page.number, on_each_side=2, on_ends=1
|
||||
),
|
||||
"form": forms.CreateInviteForm(),
|
||||
}
|
||||
return TemplateResponse(request, "settings/manage_invites.html", data)
|
||||
@ -118,15 +122,16 @@ class ManageInviteRequests(View):
|
||||
reduce(operator.or_, (Q(**f) for f in filters))
|
||||
).distinct()
|
||||
|
||||
paginated = Paginator(
|
||||
requests,
|
||||
PAGE_LENGTH,
|
||||
)
|
||||
paginated = Paginator(requests, PAGE_LENGTH)
|
||||
|
||||
page = paginated.get_page(request.GET.get("page"))
|
||||
data = {
|
||||
"ignored": ignored,
|
||||
"count": paginated.count,
|
||||
"requests": paginated.get_page(request.GET.get("page")),
|
||||
"requests": page,
|
||||
"page_range": paginated.get_elided_page_range(
|
||||
page.number, on_each_side=2, on_ends=1
|
||||
),
|
||||
"sort": sort,
|
||||
}
|
||||
return TemplateResponse(request, "settings/manage_invite_requests.html", data)
|
||||
|
@ -1,13 +1,8 @@
|
||||
""" isbn search view """
|
||||
from django.http import HttpResponseNotFound
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
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 bookwyrm import forms, models
|
||||
from bookwyrm.connectors import connector_manager
|
||||
from .helpers import is_api_request
|
||||
|
||||
@ -23,7 +18,6 @@ class Isbn(View):
|
||||
return JsonResponse([r.json() for r in book_results], safe=False)
|
||||
|
||||
data = {
|
||||
"title": "ISBN Search Results",
|
||||
"results": book_results,
|
||||
"query": isbn,
|
||||
}
|
||||
|
@ -314,8 +314,7 @@ def set_book_position(request, list_item_id):
|
||||
Max("order")
|
||||
)["order__max"]
|
||||
|
||||
if int_position > order_max:
|
||||
int_position = order_max
|
||||
int_position = min(int_position, order_max)
|
||||
|
||||
if request.user not in (book_list.user, list_item.user):
|
||||
return HttpResponseNotFound()
|
||||
|
@ -7,95 +7,79 @@ from dateutil.parser import ParserError
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import HttpResponseBadRequest, HttpResponseNotFound
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
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 bookwyrm import models
|
||||
from .helpers import get_edition, handle_reading_status
|
||||
from .shelf import handle_unshelve
|
||||
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
@login_required
|
||||
@require_POST
|
||||
def start_reading(request, book_id):
|
||||
"""begin reading a book"""
|
||||
book = get_edition(book_id)
|
||||
reading_shelf = models.Shelf.objects.filter(
|
||||
identifier=models.Shelf.READING, user=request.user
|
||||
).first()
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
# pylint: disable=no-self-use
|
||||
class ReadingStatus(View):
|
||||
"""consider reading a book"""
|
||||
|
||||
# create a readthrough
|
||||
readthrough = update_readthrough(request, book=book)
|
||||
if readthrough:
|
||||
readthrough.save()
|
||||
def get(self, request, status, book_id):
|
||||
"""modal page"""
|
||||
book = get_edition(book_id)
|
||||
template = {
|
||||
"want": "want.html",
|
||||
"start": "start.html",
|
||||
"finish": "finish.html",
|
||||
}.get(status)
|
||||
if not template:
|
||||
return HttpResponseNotFound()
|
||||
return TemplateResponse(request, f"reading_progress/{template}", {"book": book})
|
||||
|
||||
# create a progress update if we have a page
|
||||
readthrough.create_update()
|
||||
def post(self, request, status, book_id):
|
||||
"""desire a book"""
|
||||
identifier = {
|
||||
"want": models.Shelf.TO_READ,
|
||||
"start": models.Shelf.READING,
|
||||
"finish": models.Shelf.READ_FINISHED,
|
||||
}.get(status)
|
||||
if not identifier:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
current_status_shelfbook = (
|
||||
models.ShelfBook.objects.select_related("shelf")
|
||||
.filter(
|
||||
shelf__identifier__in=models.Shelf.READ_STATUS_IDENTIFIERS,
|
||||
user=request.user,
|
||||
book=book,
|
||||
desired_shelf = models.Shelf.objects.filter(
|
||||
identifier=identifier, user=request.user
|
||||
).first()
|
||||
|
||||
book = get_edition(book_id)
|
||||
|
||||
current_status_shelfbook = (
|
||||
models.ShelfBook.objects.select_related("shelf")
|
||||
.filter(
|
||||
shelf__identifier__in=models.Shelf.READ_STATUS_IDENTIFIERS,
|
||||
user=request.user,
|
||||
book=book,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if current_status_shelfbook is not None:
|
||||
if current_status_shelfbook.shelf.identifier != models.Shelf.READING:
|
||||
handle_unshelve(book, current_status_shelfbook.shelf)
|
||||
else: # It already was on the shelf
|
||||
return redirect(request.headers.get("Referer", "/"))
|
||||
if current_status_shelfbook is not None:
|
||||
if current_status_shelfbook.shelf.identifier != desired_shelf.identifier:
|
||||
current_status_shelfbook.delete()
|
||||
else: # It already was on the shelf
|
||||
return redirect(request.headers.get("Referer", "/"))
|
||||
|
||||
models.ShelfBook.objects.create(book=book, shelf=reading_shelf, user=request.user)
|
||||
|
||||
# post about it (if you want)
|
||||
if request.POST.get("post-status"):
|
||||
privacy = request.POST.get("privacy")
|
||||
handle_reading_status(request.user, reading_shelf, book, privacy)
|
||||
|
||||
return redirect(request.headers.get("Referer", "/"))
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def finish_reading(request, book_id):
|
||||
"""a user completed a book, yay"""
|
||||
book = get_edition(book_id)
|
||||
finished_read_shelf = models.Shelf.objects.filter(
|
||||
identifier=models.Shelf.READ_FINISHED, user=request.user
|
||||
).first()
|
||||
|
||||
# update or create a readthrough
|
||||
readthrough = update_readthrough(request, book=book)
|
||||
if readthrough:
|
||||
readthrough.save()
|
||||
|
||||
current_status_shelfbook = (
|
||||
models.ShelfBook.objects.select_related("shelf")
|
||||
.filter(
|
||||
shelf__identifier__in=models.Shelf.READ_STATUS_IDENTIFIERS,
|
||||
user=request.user,
|
||||
book=book,
|
||||
models.ShelfBook.objects.create(
|
||||
book=book, shelf=desired_shelf, user=request.user
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if current_status_shelfbook is not None:
|
||||
if current_status_shelfbook.shelf.identifier != models.Shelf.READ_FINISHED:
|
||||
handle_unshelve(book, current_status_shelfbook.shelf)
|
||||
else: # It already was on the shelf
|
||||
return redirect(request.headers.get("Referer", "/"))
|
||||
|
||||
models.ShelfBook.objects.create(
|
||||
book=book, shelf=finished_read_shelf, user=request.user
|
||||
)
|
||||
if desired_shelf.identifier != models.Shelf.TO_READ:
|
||||
# update or create a readthrough
|
||||
readthrough = update_readthrough(request, book=book)
|
||||
if readthrough:
|
||||
readthrough.save()
|
||||
|
||||
# post about it (if you want)
|
||||
if request.POST.get("post-status"):
|
||||
privacy = request.POST.get("privacy")
|
||||
handle_reading_status(request.user, finished_read_shelf, book, privacy)
|
||||
# post about it (if you want)
|
||||
if request.POST.get("post-status"):
|
||||
privacy = request.POST.get("privacy")
|
||||
handle_reading_status(request.user, desired_shelf, book, privacy)
|
||||
|
||||
return redirect(request.headers.get("Referer", "/"))
|
||||
return redirect(request.headers.get("Referer", "/"))
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -10,7 +10,7 @@ class RssFeed(Feed):
|
||||
description_template = "rss/content.html"
|
||||
title_template = "rss/title.html"
|
||||
|
||||
def get_object(self, request, username):
|
||||
def get_object(self, request, username): # pylint: disable=arguments-differ
|
||||
"""the user who's posts get serialized"""
|
||||
return get_user_from_username(request.user, username)
|
||||
|
||||
|
@ -83,7 +83,7 @@ def user_search(query, viewer, *_):
|
||||
|
||||
# use webfinger for mastodon style account@domain.com username to load the user if
|
||||
# they don't exist locally (handle_remote_webfinger will check the db)
|
||||
if re.match(regex.full_username, query):
|
||||
if re.match(regex.FULL_USERNAME, query):
|
||||
handle_remote_webfinger(query)
|
||||
|
||||
return (
|
||||
|
@ -2,7 +2,7 @@
|
||||
from collections import namedtuple
|
||||
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import Count, OuterRef, Subquery, F, Q
|
||||
from django.db.models import OuterRef, Subquery
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.paginator import Paginator
|
||||
from django.http import HttpResponseBadRequest, HttpResponseNotFound
|
||||
@ -17,10 +17,10 @@ from bookwyrm import forms, models
|
||||
from bookwyrm.activitypub import ActivitypubResponse
|
||||
from bookwyrm.settings import PAGE_LENGTH
|
||||
from .helpers import is_api_request, get_edition, get_user_from_username
|
||||
from .helpers import handle_reading_status, privacy_filter
|
||||
from .helpers import privacy_filter
|
||||
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
# pylint: disable=no-self-use
|
||||
class Shelf(View):
|
||||
"""shelf page"""
|
||||
|
||||
@ -28,7 +28,12 @@ class Shelf(View):
|
||||
"""display a shelf"""
|
||||
user = get_user_from_username(request.user, username)
|
||||
|
||||
shelves = privacy_filter(request.user, user.shelf_set)
|
||||
is_self = user == request.user
|
||||
|
||||
if is_self:
|
||||
shelves = user.shelf_set
|
||||
else:
|
||||
shelves = privacy_filter(request.user, user.shelf_set)
|
||||
|
||||
# get the shelf and make sure the logged in user should be able to see it
|
||||
if shelf_identifier:
|
||||
@ -53,26 +58,29 @@ class Shelf(View):
|
||||
if is_api_request(request):
|
||||
return ActivitypubResponse(shelf.to_activity(**request.GET))
|
||||
|
||||
reviews = privacy_filter(
|
||||
request.user,
|
||||
models.Review.objects.filter(
|
||||
user=user,
|
||||
rating__isnull=False,
|
||||
book__id=OuterRef("id"),
|
||||
),
|
||||
reviews = models.Review.objects.filter(
|
||||
user=user,
|
||||
rating__isnull=False,
|
||||
book__id=OuterRef("id"),
|
||||
deleted=False,
|
||||
).order_by("-published_date")
|
||||
|
||||
books = books.annotate(rating=Subquery(reviews.values("rating")[:1]))
|
||||
if not is_self:
|
||||
reviews = privacy_filter(request.user, reviews)
|
||||
|
||||
books = books.annotate(
|
||||
rating=Subquery(reviews.values("rating")[:1])
|
||||
).prefetch_related("authors")
|
||||
|
||||
paginated = Paginator(
|
||||
books.order_by("-updated_date"),
|
||||
books.order_by("-shelfbook__updated_date"),
|
||||
PAGE_LENGTH,
|
||||
)
|
||||
|
||||
page = paginated.get_page(request.GET.get("page"))
|
||||
data = {
|
||||
"user": user,
|
||||
"is_self": request.user == user,
|
||||
"is_self": is_self,
|
||||
"shelves": shelves.all(),
|
||||
"shelf": shelf,
|
||||
"books": page,
|
||||
@ -170,11 +178,6 @@ def shelve(request):
|
||||
models.ShelfBook.objects.create(
|
||||
book=book, shelf=desired_shelf, user=request.user
|
||||
)
|
||||
if desired_shelf.identifier == models.Shelf.TO_READ and request.POST.get(
|
||||
"post-status"
|
||||
):
|
||||
privacy = request.POST.get("privacy") or desired_shelf.privacy
|
||||
handle_reading_status(request.user, desired_shelf, book, privacy=privacy)
|
||||
else:
|
||||
try:
|
||||
models.ShelfBook.objects.create(
|
||||
@ -198,7 +201,6 @@ def unshelve(request):
|
||||
return redirect(request.headers.get("Referer", "/"))
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def handle_unshelve(book, shelf):
|
||||
"""unshelve a book"""
|
||||
row = models.ShelfBook.objects.get(book=book, shelf=shelf)
|
||||
|
@ -133,7 +133,7 @@ def find_mentions(content):
|
||||
"""detect @mentions in raw status content"""
|
||||
if not content:
|
||||
return
|
||||
for match in re.finditer(regex.strict_username, content):
|
||||
for match in re.finditer(regex.STRICT_USERNAME, content):
|
||||
username = match.group().strip().split("@")[1:]
|
||||
if len(username) == 1:
|
||||
# this looks like a local user (@user), fill in the domain
|
||||
@ -150,7 +150,7 @@ def find_mentions(content):
|
||||
def format_links(content):
|
||||
"""detect and format links"""
|
||||
return re.sub(
|
||||
r'([^(href=")]|^|\()(https?:\/\/(%s([\w\.\-_\/+&\?=:;,])*))' % regex.domain,
|
||||
r'([^(href=")]|^|\()(https?:\/\/(%s([\w\.\-_\/+&\?=:;,])*))' % regex.DOMAIN,
|
||||
r'\g<1><a href="\g<2>">\g<3></a>',
|
||||
content,
|
||||
)
|
||||
|
@ -1,25 +1,17 @@
|
||||
""" non-interactive pages """
|
||||
from io import BytesIO
|
||||
from uuid import uuid4
|
||||
from PIL import Image
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.paginator import Paginator
|
||||
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 bookwyrm import forms, models
|
||||
from bookwyrm import models
|
||||
from bookwyrm.activitypub import ActivitypubResponse
|
||||
from bookwyrm.settings import PAGE_LENGTH
|
||||
from .helpers import get_user_from_username, is_api_request
|
||||
from .helpers import privacy_filter
|
||||
|
||||
|
||||
# pylint: disable= no-self-use
|
||||
# pylint: disable=no-self-use
|
||||
class User(View):
|
||||
"""user profile page"""
|
||||
|
||||
@ -59,10 +51,15 @@ class User(View):
|
||||
break
|
||||
|
||||
# user's posts
|
||||
activities = privacy_filter(
|
||||
request.user,
|
||||
user.status_set.select_subclasses(),
|
||||
activities = (
|
||||
privacy_filter(
|
||||
request.user,
|
||||
user.status_set.select_subclasses(),
|
||||
)
|
||||
.select_related("reply_parent")
|
||||
.prefetch_related("mention_books", "mention_users")
|
||||
)
|
||||
|
||||
paginated = Paginator(activities, PAGE_LENGTH)
|
||||
goal = models.AnnualGoal.objects.filter(
|
||||
user=user, year=timezone.now().year
|
||||
@ -117,72 +114,3 @@ class Following(View):
|
||||
"follow_list": paginated.get_page(request.GET.get("page")),
|
||||
}
|
||||
return TemplateResponse(request, "user/relationships/following.html", data)
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class EditUser(View):
|
||||
"""edit user view"""
|
||||
|
||||
def get(self, request):
|
||||
"""edit profile page for a user"""
|
||||
data = {
|
||||
"form": forms.EditUserForm(instance=request.user),
|
||||
"user": request.user,
|
||||
}
|
||||
return TemplateResponse(request, "preferences/edit_user.html", data)
|
||||
|
||||
def post(self, request):
|
||||
"""les get fancy with images"""
|
||||
form = forms.EditUserForm(request.POST, request.FILES, instance=request.user)
|
||||
if not form.is_valid():
|
||||
data = {"form": form, "user": request.user}
|
||||
return TemplateResponse(request, "preferences/edit_user.html", data)
|
||||
|
||||
user = save_user_form(form)
|
||||
|
||||
return redirect(user.local_path)
|
||||
|
||||
|
||||
def save_user_form(form):
|
||||
"""special handling for the user form"""
|
||||
user = form.save(commit=False)
|
||||
|
||||
if "avatar" in form.files:
|
||||
# crop and resize avatar upload
|
||||
image = Image.open(form.files["avatar"])
|
||||
image = crop_avatar(image)
|
||||
|
||||
# set the name to a hash
|
||||
extension = form.files["avatar"].name.split(".")[-1]
|
||||
filename = "%s.%s" % (uuid4(), extension)
|
||||
user.avatar.save(filename, image, save=False)
|
||||
|
||||
user.save()
|
||||
return user
|
||||
|
||||
|
||||
def crop_avatar(image):
|
||||
"""reduce the size and make an avatar square"""
|
||||
target_size = 120
|
||||
width, height = image.size
|
||||
thumbnail_scale = (
|
||||
height / (width / target_size)
|
||||
if height > width
|
||||
else width / (height / target_size)
|
||||
)
|
||||
image.thumbnail([thumbnail_scale, thumbnail_scale])
|
||||
width, height = image.size
|
||||
|
||||
width_diff = width - target_size
|
||||
height_diff = height - target_size
|
||||
cropped = image.crop(
|
||||
(
|
||||
int(width_diff / 2),
|
||||
int(height_diff / 2),
|
||||
int(width - (width_diff / 2)),
|
||||
int(height - (height_diff / 2)),
|
||||
)
|
||||
)
|
||||
output = BytesIO()
|
||||
cropped.save(output, format=image.format)
|
||||
return ContentFile(output.getvalue())
|
||||
|
Reference in New Issue
Block a user