forked from mirrors/bookwyrm
Adds shelf views
This commit is contained in:
parent
20e280e676
commit
beeeaaaf39
8 changed files with 414 additions and 214 deletions
|
@ -115,49 +115,6 @@ def handle_reject(follow_request):
|
||||||
broadcast(to_follow, activity, privacy='direct', direct_recipients=[user])
|
broadcast(to_follow, activity, privacy='direct', direct_recipients=[user])
|
||||||
|
|
||||||
|
|
||||||
def handle_shelve(user, book, shelf):
|
|
||||||
''' a local user is getting a book put on their shelf '''
|
|
||||||
# update the database
|
|
||||||
shelve = models.ShelfBook(book=book, shelf=shelf, added_by=user)
|
|
||||||
shelve.save()
|
|
||||||
|
|
||||||
broadcast(user, shelve.to_add_activity(user))
|
|
||||||
|
|
||||||
|
|
||||||
def handle_unshelve(user, book, shelf):
|
|
||||||
''' a local user is getting a book put on their shelf '''
|
|
||||||
# update the database
|
|
||||||
row = models.ShelfBook.objects.get(book=book, shelf=shelf)
|
|
||||||
activity = row.to_remove_activity(user)
|
|
||||||
row.delete()
|
|
||||||
|
|
||||||
broadcast(user, activity)
|
|
||||||
|
|
||||||
|
|
||||||
def handle_reading_status(user, shelf, book, privacy):
|
|
||||||
''' post about a user reading a book '''
|
|
||||||
# tell the world about this cool thing that happened
|
|
||||||
try:
|
|
||||||
message = {
|
|
||||||
'to-read': 'wants to read',
|
|
||||||
'reading': 'started reading',
|
|
||||||
'read': 'finished reading'
|
|
||||||
}[shelf.identifier]
|
|
||||||
except KeyError:
|
|
||||||
# it's a non-standard shelf, don't worry about it
|
|
||||||
return
|
|
||||||
|
|
||||||
status = create_generated_note(
|
|
||||||
user,
|
|
||||||
message,
|
|
||||||
mention_books=[book],
|
|
||||||
privacy=privacy
|
|
||||||
)
|
|
||||||
status.save()
|
|
||||||
|
|
||||||
broadcast(user, status.to_create_activity(user))
|
|
||||||
|
|
||||||
|
|
||||||
def handle_imported_book(user, item, include_reviews, privacy):
|
def handle_imported_book(user, item, include_reviews, privacy):
|
||||||
''' process a goodreads csv and then post about it '''
|
''' process a goodreads csv and then post about it '''
|
||||||
if isinstance(item.book, models.Work):
|
if isinstance(item.book, models.Work):
|
||||||
|
|
192
bookwyrm/tests/views/test_shelf.py
Normal file
192
bookwyrm/tests/views/test_shelf.py
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
''' test for app action functionality '''
|
||||||
|
from unittest.mock import patch
|
||||||
|
from django.template.response import TemplateResponse
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.test.client import RequestFactory
|
||||||
|
|
||||||
|
from bookwyrm import models, views
|
||||||
|
from bookwyrm.activitypub import ActivitypubResponse
|
||||||
|
|
||||||
|
|
||||||
|
class ShelfViews(TestCase):
|
||||||
|
''' tag views'''
|
||||||
|
def setUp(self):
|
||||||
|
''' we need basic test data and mocks '''
|
||||||
|
self.factory = RequestFactory()
|
||||||
|
self.local_user = models.User.objects.create_user(
|
||||||
|
'mouse@local.com', 'mouse@mouse.com', 'mouseword',
|
||||||
|
local=True, localname='mouse',
|
||||||
|
remote_id='https://example.com/users/mouse',
|
||||||
|
)
|
||||||
|
self.work = models.Work.objects.create(title='Test Work')
|
||||||
|
self.book = models.Edition.objects.create(
|
||||||
|
title='Example Edition',
|
||||||
|
remote_id='https://example.com/book/1',
|
||||||
|
parent_work=self.work
|
||||||
|
)
|
||||||
|
self.shelf = models.Shelf.objects.create(
|
||||||
|
name='Test Shelf',
|
||||||
|
identifier='test-shelf',
|
||||||
|
user=self.local_user
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_shelf_page(self):
|
||||||
|
''' there are so many views, this just makes sure it LOADS '''
|
||||||
|
view = views.Shelf.as_view()
|
||||||
|
shelf = self.local_user.shelf_set.first()
|
||||||
|
request = self.factory.get('')
|
||||||
|
request.user = self.local_user
|
||||||
|
with patch('bookwyrm.views.shelf.is_api_request') as is_api:
|
||||||
|
is_api.return_value = False
|
||||||
|
result = view(request, self.local_user.username, shelf.identifier)
|
||||||
|
self.assertIsInstance(result, TemplateResponse)
|
||||||
|
self.assertEqual(result.template_name, 'shelf.html')
|
||||||
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
|
with patch('bookwyrm.views.shelf.is_api_request') as is_api:
|
||||||
|
is_api.return_value = True
|
||||||
|
result = view(
|
||||||
|
request, self.local_user.username, shelf.identifier)
|
||||||
|
self.assertIsInstance(result, ActivitypubResponse)
|
||||||
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
|
request = self.factory.get('/?page=1')
|
||||||
|
request.user = self.local_user
|
||||||
|
with patch('bookwyrm.views.shelf.is_api_request') as is_api:
|
||||||
|
is_api.return_value = True
|
||||||
|
result = view(
|
||||||
|
request, self.local_user.username, shelf.identifier)
|
||||||
|
self.assertIsInstance(result, ActivitypubResponse)
|
||||||
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
|
def test_edit_shelf_privacy(self):
|
||||||
|
''' set name or privacy on shelf '''
|
||||||
|
view = views.Shelf.as_view()
|
||||||
|
shelf = self.local_user.shelf_set.get(identifier='to-read')
|
||||||
|
self.assertEqual(shelf.privacy, 'public')
|
||||||
|
|
||||||
|
request = self.factory.post(
|
||||||
|
'', {
|
||||||
|
'privacy': 'unlisted',
|
||||||
|
'user': self.local_user.id,
|
||||||
|
'name': 'To Read',
|
||||||
|
})
|
||||||
|
request.user = self.local_user
|
||||||
|
view(request, self.local_user.username, shelf.id)
|
||||||
|
shelf.refresh_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(shelf.privacy, 'unlisted')
|
||||||
|
|
||||||
|
|
||||||
|
def test_edit_shelf_name(self):
|
||||||
|
''' change the name of an editable shelf '''
|
||||||
|
view = views.Shelf.as_view()
|
||||||
|
shelf = models.Shelf.objects.create(
|
||||||
|
name='Test Shelf', user=self.local_user)
|
||||||
|
self.assertEqual(shelf.privacy, 'public')
|
||||||
|
|
||||||
|
request = self.factory.post(
|
||||||
|
'', {
|
||||||
|
'privacy': 'public',
|
||||||
|
'user': self.local_user.id,
|
||||||
|
'name': 'cool name'
|
||||||
|
})
|
||||||
|
request.user = self.local_user
|
||||||
|
view(request, request.user.username, shelf.id)
|
||||||
|
shelf.refresh_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(shelf.name, 'cool name')
|
||||||
|
self.assertEqual(shelf.identifier, 'testshelf-%d' % shelf.id)
|
||||||
|
|
||||||
|
|
||||||
|
def test_edit_shelf_name_not_editable(self):
|
||||||
|
''' can't change the name of an non-editable shelf '''
|
||||||
|
view = views.Shelf.as_view()
|
||||||
|
shelf = self.local_user.shelf_set.get(identifier='to-read')
|
||||||
|
self.assertEqual(shelf.privacy, 'public')
|
||||||
|
|
||||||
|
request = self.factory.post(
|
||||||
|
'', {
|
||||||
|
'privacy': 'public',
|
||||||
|
'user': self.local_user.id,
|
||||||
|
'name': 'cool name'
|
||||||
|
})
|
||||||
|
request.user = self.local_user
|
||||||
|
view(request, request.user.username, shelf.id)
|
||||||
|
|
||||||
|
self.assertEqual(shelf.name, 'To Read')
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_shelve(self):
|
||||||
|
''' shelve a book '''
|
||||||
|
request = self.factory.post('', {
|
||||||
|
'book': self.book.id,
|
||||||
|
'shelf': self.shelf.identifier
|
||||||
|
})
|
||||||
|
request.user = self.local_user
|
||||||
|
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
||||||
|
views.shelve(request)
|
||||||
|
# make sure the book is on the shelf
|
||||||
|
self.assertEqual(self.shelf.books.get(), self.book)
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_shelve_to_read(self):
|
||||||
|
''' special behavior for the to-read shelf '''
|
||||||
|
shelf = models.Shelf.objects.get(identifier='to-read')
|
||||||
|
request = self.factory.post('', {
|
||||||
|
'book': self.book.id,
|
||||||
|
'shelf': shelf.identifier
|
||||||
|
})
|
||||||
|
request.user = self.local_user
|
||||||
|
|
||||||
|
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
||||||
|
views.shelve(request)
|
||||||
|
# make sure the book is on the shelf
|
||||||
|
self.assertEqual(shelf.books.get(), self.book)
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_shelve_reading(self):
|
||||||
|
''' special behavior for the reading shelf '''
|
||||||
|
shelf = models.Shelf.objects.get(identifier='reading')
|
||||||
|
request = self.factory.post('', {
|
||||||
|
'book': self.book.id,
|
||||||
|
'shelf': shelf.identifier
|
||||||
|
})
|
||||||
|
request.user = self.local_user
|
||||||
|
|
||||||
|
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
||||||
|
views.shelve(request)
|
||||||
|
# make sure the book is on the shelf
|
||||||
|
self.assertEqual(shelf.books.get(), self.book)
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_shelve_read(self):
|
||||||
|
''' special behavior for the read shelf '''
|
||||||
|
shelf = models.Shelf.objects.get(identifier='read')
|
||||||
|
request = self.factory.post('', {
|
||||||
|
'book': self.book.id,
|
||||||
|
'shelf': shelf.identifier
|
||||||
|
})
|
||||||
|
request.user = self.local_user
|
||||||
|
|
||||||
|
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
||||||
|
views.shelve(request)
|
||||||
|
# make sure the book is on the shelf
|
||||||
|
self.assertEqual(shelf.books.get(), self.book)
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_unshelve(self):
|
||||||
|
''' remove a book from a shelf '''
|
||||||
|
self.shelf.books.add(self.book)
|
||||||
|
self.shelf.save()
|
||||||
|
self.assertEqual(self.shelf.books.count(), 1)
|
||||||
|
request = self.factory.post('', {
|
||||||
|
'book': self.book.id,
|
||||||
|
'shelf': self.shelf.id
|
||||||
|
})
|
||||||
|
request.user = self.local_user
|
||||||
|
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
||||||
|
views.unshelve(request)
|
||||||
|
self.assertEqual(self.shelf.books.count(), 0)
|
|
@ -68,7 +68,7 @@ urlpatterns = [
|
||||||
# users
|
# users
|
||||||
re_path(r'%s/?$' % user_path, views.User.as_view()),
|
re_path(r'%s/?$' % user_path, views.User.as_view()),
|
||||||
re_path(r'%s\.json$' % user_path, views.User.as_view()),
|
re_path(r'%s\.json$' % user_path, views.User.as_view()),
|
||||||
re_path(r'%s/shelves/?$' % user_path, vviews.user_shelves_page),
|
re_path(r'%s/shelves/?$' % user_path, views.user_shelves_page),
|
||||||
re_path(r'%s/followers(.json)?/?$' % user_path, views.Followers.as_view()),
|
re_path(r'%s/followers(.json)?/?$' % user_path, views.Followers.as_view()),
|
||||||
re_path(r'%s/following(.json)?/?$' % user_path, views.Following.as_view()),
|
re_path(r'%s/following(.json)?/?$' % user_path, views.Following.as_view()),
|
||||||
re_path(r'^edit-profile/?$', views.EditUser.as_view()),
|
re_path(r'^edit-profile/?$', views.EditUser.as_view()),
|
||||||
|
@ -106,10 +106,15 @@ urlpatterns = [
|
||||||
re_path(r'^tag/?$', views.AddTag.as_view()),
|
re_path(r'^tag/?$', views.AddTag.as_view()),
|
||||||
re_path(r'^untag/?$', views.RemoveTag.as_view()),
|
re_path(r'^untag/?$', views.RemoveTag.as_view()),
|
||||||
|
|
||||||
|
# shelf
|
||||||
re_path(r'^%s/shelf/(?P<shelf_identifier>[\w-]+)(.json)?/?$' % \
|
re_path(r'^%s/shelf/(?P<shelf_identifier>[\w-]+)(.json)?/?$' % \
|
||||||
user_path, vviews.shelf_page),
|
user_path, views.Shelf.as_view()),
|
||||||
re_path(r'^%s/shelf/(?P<shelf_identifier>[\w-]+)(.json)?/?$' % \
|
re_path(r'^%s/shelf/(?P<shelf_identifier>[\w-]+)(.json)?/?$' % \
|
||||||
local_user_path, vviews.shelf_page),
|
local_user_path, views.Shelf.as_view()),
|
||||||
|
re_path(r'^create-shelf/?$', views.create_shelf),
|
||||||
|
re_path(r'^delete-shelf/(?P<shelf_id>\d+)?$', views.delete_shelf),
|
||||||
|
re_path(r'^shelve/?$', views.shelve),
|
||||||
|
re_path(r'^unshelve/?$', views.unshelve),
|
||||||
|
|
||||||
re_path(r'^search/?$', vviews.search),
|
re_path(r'^search/?$', vviews.search),
|
||||||
|
|
||||||
|
@ -117,11 +122,6 @@ urlpatterns = [
|
||||||
re_path(r'^delete-readthrough/?$', actions.delete_readthrough),
|
re_path(r'^delete-readthrough/?$', actions.delete_readthrough),
|
||||||
re_path(r'^create-readthrough/?$', actions.create_readthrough),
|
re_path(r'^create-readthrough/?$', actions.create_readthrough),
|
||||||
|
|
||||||
re_path(r'^create-shelf/?$', actions.create_shelf),
|
|
||||||
re_path(r'^edit-shelf/(?P<shelf_id>\d+)?$', actions.edit_shelf),
|
|
||||||
re_path(r'^delete-shelf/(?P<shelf_id>\d+)?$', actions.delete_shelf),
|
|
||||||
re_path(r'^shelve/?$', actions.shelve),
|
|
||||||
re_path(r'^unshelve/?$', actions.unshelve),
|
|
||||||
re_path(r'^start-reading/(?P<book_id>\d+)/?$', actions.start_reading),
|
re_path(r'^start-reading/(?P<book_id>\d+)/?$', actions.start_reading),
|
||||||
re_path(r'^finish-reading/(?P<book_id>\d+)/?$', actions.finish_reading),
|
re_path(r'^finish-reading/(?P<book_id>\d+)/?$', actions.finish_reading),
|
||||||
|
|
||||||
|
|
|
@ -8,95 +8,22 @@ from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.views.decorators.http import require_POST
|
from django.views.decorators.http import require_POST
|
||||||
|
|
||||||
from bookwyrm import forms, models, outgoing
|
from bookwyrm import models, outgoing
|
||||||
from bookwyrm.vviews import get_user_from_username, get_edition
|
|
||||||
|
|
||||||
@login_required
|
def get_edition(book_id):
|
||||||
@require_POST
|
''' look up a book in the db and return an edition '''
|
||||||
def create_shelf(request):
|
book = models.Book.objects.select_subclasses().get(id=book_id)
|
||||||
''' user generated shelves '''
|
if isinstance(book, models.Work):
|
||||||
form = forms.ShelfForm(request.POST)
|
book = book.get_default_edition()
|
||||||
if not form.is_valid():
|
return book
|
||||||
return redirect(request.headers.get('Referer', '/'))
|
|
||||||
|
|
||||||
shelf = form.save()
|
def get_user_from_username(username):
|
||||||
return redirect('/user/%s/shelf/%s' % \
|
''' helper function to resolve a localname or a username to a user '''
|
||||||
(request.user.localname, shelf.identifier))
|
# raises DoesNotExist if user is now found
|
||||||
|
try:
|
||||||
|
return models.User.objects.get(localname=username)
|
||||||
@login_required
|
except models.User.DoesNotExist:
|
||||||
@require_POST
|
return models.User.objects.get(username=username)
|
||||||
def edit_shelf(request, shelf_id):
|
|
||||||
''' user generated shelves '''
|
|
||||||
shelf = get_object_or_404(models.Shelf, id=shelf_id)
|
|
||||||
if request.user != shelf.user:
|
|
||||||
return HttpResponseBadRequest()
|
|
||||||
if not shelf.editable and request.POST.get('name') != shelf.name:
|
|
||||||
return HttpResponseBadRequest()
|
|
||||||
|
|
||||||
form = forms.ShelfForm(request.POST, instance=shelf)
|
|
||||||
if not form.is_valid():
|
|
||||||
return redirect(shelf.local_path)
|
|
||||||
shelf = form.save()
|
|
||||||
return redirect(shelf.local_path)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
@require_POST
|
|
||||||
def delete_shelf(request, shelf_id):
|
|
||||||
''' user generated shelves '''
|
|
||||||
shelf = get_object_or_404(models.Shelf, id=shelf_id)
|
|
||||||
if request.user != shelf.user or not shelf.editable:
|
|
||||||
return HttpResponseBadRequest()
|
|
||||||
|
|
||||||
shelf.delete()
|
|
||||||
return redirect('/user/%s/shelves' % request.user.localname)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
@require_POST
|
|
||||||
def shelve(request):
|
|
||||||
''' put a on a user's shelf '''
|
|
||||||
book = get_edition(request.POST['book'])
|
|
||||||
|
|
||||||
desired_shelf = models.Shelf.objects.filter(
|
|
||||||
identifier=request.POST['shelf'],
|
|
||||||
user=request.user
|
|
||||||
).first()
|
|
||||||
|
|
||||||
if request.POST.get('reshelve', True):
|
|
||||||
try:
|
|
||||||
current_shelf = models.Shelf.objects.get(
|
|
||||||
user=request.user,
|
|
||||||
edition=book
|
|
||||||
)
|
|
||||||
outgoing.handle_unshelve(request.user, book, current_shelf)
|
|
||||||
except models.Shelf.DoesNotExist:
|
|
||||||
# this just means it isn't currently on the user's shelves
|
|
||||||
pass
|
|
||||||
outgoing.handle_shelve(request.user, book, desired_shelf)
|
|
||||||
|
|
||||||
# post about "want to read" shelves
|
|
||||||
if desired_shelf.identifier == 'to-read':
|
|
||||||
outgoing.handle_reading_status(
|
|
||||||
request.user,
|
|
||||||
desired_shelf,
|
|
||||||
book,
|
|
||||||
privacy=desired_shelf.privacy
|
|
||||||
)
|
|
||||||
|
|
||||||
return redirect('/')
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
@require_POST
|
|
||||||
def unshelve(request):
|
|
||||||
''' put a on a user's shelf '''
|
|
||||||
book = models.Edition.objects.get(id=request.POST['book'])
|
|
||||||
current_shelf = models.Shelf.objects.get(id=request.POST['shelf'])
|
|
||||||
|
|
||||||
outgoing.handle_unshelve(request.user, book, current_shelf)
|
|
||||||
return redirect(request.headers.get('Referer', '/'))
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
|
|
@ -13,3 +13,6 @@ from .books import Book, EditBook, Editions
|
||||||
from .books import upload_cover, add_description, switch_edition, resolve_book
|
from .books import upload_cover, add_description, switch_edition, resolve_book
|
||||||
from .author import Author, EditAuthor
|
from .author import Author, EditAuthor
|
||||||
from .tag import Tag, AddTag, RemoveTag
|
from .tag import Tag, AddTag, RemoveTag
|
||||||
|
from .shelf import Shelf
|
||||||
|
from .shelf import user_shelves_page, create_shelf, delete_shelf
|
||||||
|
from .shelf import shelve, unshelve
|
||||||
|
|
|
@ -4,7 +4,9 @@ from requests import HTTPError
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from bookwyrm import activitypub, models
|
from bookwyrm import activitypub, models
|
||||||
|
from bookwyrm.broadcast import broadcast
|
||||||
from bookwyrm.connectors import ConnectorException, get_data
|
from bookwyrm.connectors import ConnectorException, get_data
|
||||||
|
from bookwyrm.status import create_generated_note
|
||||||
from bookwyrm.utils import regex
|
from bookwyrm.utils import regex
|
||||||
|
|
||||||
|
|
||||||
|
@ -147,3 +149,25 @@ def get_edition(book_id):
|
||||||
return book
|
return book
|
||||||
|
|
||||||
|
|
||||||
|
def handle_reading_status(user, shelf, book, privacy):
|
||||||
|
''' post about a user reading a book '''
|
||||||
|
# tell the world about this cool thing that happened
|
||||||
|
try:
|
||||||
|
message = {
|
||||||
|
'to-read': 'wants to read',
|
||||||
|
'reading': 'started reading',
|
||||||
|
'read': 'finished reading'
|
||||||
|
}[shelf.identifier]
|
||||||
|
except KeyError:
|
||||||
|
# it's a non-standard shelf, don't worry about it
|
||||||
|
return
|
||||||
|
|
||||||
|
status = create_generated_note(
|
||||||
|
user,
|
||||||
|
message,
|
||||||
|
mention_books=[book],
|
||||||
|
privacy=privacy
|
||||||
|
)
|
||||||
|
status.save()
|
||||||
|
|
||||||
|
broadcast(user, status.to_create_activity(user))
|
||||||
|
|
172
bookwyrm/views/shelf.py
Normal file
172
bookwyrm/views/shelf.py
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
''' shelf views'''
|
||||||
|
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 forms, models
|
||||||
|
from bookwyrm.activitypub import ActivitypubResponse
|
||||||
|
from bookwyrm.broadcast import broadcast
|
||||||
|
from .helpers import is_api_request, get_edition, get_user_from_username
|
||||||
|
from .helpers import handle_reading_status
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable= no-self-use
|
||||||
|
class Shelf(View):
|
||||||
|
''' shelf page '''
|
||||||
|
def get(self, request, username, shelf_identifier):
|
||||||
|
''' display a shelf '''
|
||||||
|
try:
|
||||||
|
user = get_user_from_username(username)
|
||||||
|
except models.User.DoesNotExist:
|
||||||
|
return HttpResponseNotFound()
|
||||||
|
|
||||||
|
if shelf_identifier:
|
||||||
|
shelf = user.shelf_set.get(identifier=shelf_identifier)
|
||||||
|
else:
|
||||||
|
shelf = user.shelf_set.first()
|
||||||
|
|
||||||
|
is_self = request.user == user
|
||||||
|
|
||||||
|
shelves = user.shelf_set
|
||||||
|
if not is_self:
|
||||||
|
follower = user.followers.filter(id=request.user.id).exists()
|
||||||
|
# make sure the user has permission to view the shelf
|
||||||
|
if shelf.privacy == 'direct' or \
|
||||||
|
(shelf.privacy == 'followers' and not follower):
|
||||||
|
return HttpResponseNotFound()
|
||||||
|
|
||||||
|
# only show other shelves that should be visible
|
||||||
|
if follower:
|
||||||
|
shelves = shelves.filter(privacy__in=['public', 'followers'])
|
||||||
|
else:
|
||||||
|
shelves = shelves.filter(privacy='public')
|
||||||
|
|
||||||
|
|
||||||
|
if is_api_request(request):
|
||||||
|
return ActivitypubResponse(shelf.to_activity(**request.GET))
|
||||||
|
|
||||||
|
books = models.ShelfBook.objects.filter(
|
||||||
|
added_by=user, shelf=shelf
|
||||||
|
).order_by('-updated_date').all()
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'title': '%s\'s %s shelf' % (user.display_name, shelf.name),
|
||||||
|
'user': user,
|
||||||
|
'is_self': is_self,
|
||||||
|
'shelves': shelves.all(),
|
||||||
|
'shelf': shelf,
|
||||||
|
'books': [b.book for b in books],
|
||||||
|
}
|
||||||
|
|
||||||
|
return TemplateResponse(request, 'shelf.html', data)
|
||||||
|
|
||||||
|
@method_decorator(login_required, name='dispatch')
|
||||||
|
def post(self, request, username, shelf_id):
|
||||||
|
''' user generated shelves '''
|
||||||
|
if not request.user.username == username:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
shelf = get_object_or_404(models.Shelf, id=shelf_id)
|
||||||
|
if request.user != shelf.user:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
if not shelf.editable and request.POST.get('name') != shelf.name:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
form = forms.ShelfForm(request.POST, instance=shelf)
|
||||||
|
if not form.is_valid():
|
||||||
|
return redirect(shelf.local_path)
|
||||||
|
shelf = form.save()
|
||||||
|
return redirect(shelf.local_path)
|
||||||
|
|
||||||
|
|
||||||
|
def user_shelves_page(request, username):
|
||||||
|
''' default shelf '''
|
||||||
|
return Shelf.as_view()(request, username, None)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@require_POST
|
||||||
|
def create_shelf(request):
|
||||||
|
''' user generated shelves '''
|
||||||
|
form = forms.ShelfForm(request.POST)
|
||||||
|
if not form.is_valid():
|
||||||
|
return redirect(request.headers.get('Referer', '/'))
|
||||||
|
|
||||||
|
shelf = form.save()
|
||||||
|
return redirect('/user/%s/shelf/%s' % \
|
||||||
|
(request.user.localname, shelf.identifier))
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@require_POST
|
||||||
|
def delete_shelf(request, shelf_id):
|
||||||
|
''' user generated shelves '''
|
||||||
|
shelf = get_object_or_404(models.Shelf, id=shelf_id)
|
||||||
|
if request.user != shelf.user or not shelf.editable:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
shelf.delete()
|
||||||
|
return redirect('/user/%s/shelves' % request.user.localname)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@require_POST
|
||||||
|
def shelve(request):
|
||||||
|
''' put a on a user's shelf '''
|
||||||
|
book = get_edition(request.POST.get('book'))
|
||||||
|
|
||||||
|
desired_shelf = models.Shelf.objects.filter(
|
||||||
|
identifier=request.POST.get('shelf'),
|
||||||
|
user=request.user
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if request.POST.get('reshelve', True):
|
||||||
|
try:
|
||||||
|
current_shelf = models.Shelf.objects.get(
|
||||||
|
user=request.user,
|
||||||
|
edition=book
|
||||||
|
)
|
||||||
|
handle_unshelve(request.user, book, current_shelf)
|
||||||
|
except models.Shelf.DoesNotExist:
|
||||||
|
# this just means it isn't currently on the user's shelves
|
||||||
|
pass
|
||||||
|
shelfbook = models.ShelfBook(
|
||||||
|
book=book, shelf=desired_shelf, added_by=request.user)
|
||||||
|
shelfbook.save()
|
||||||
|
|
||||||
|
broadcast(request.user, shelfbook.to_add_activity(request.user))
|
||||||
|
|
||||||
|
# post about "want to read" shelves
|
||||||
|
if desired_shelf.identifier == 'to-read':
|
||||||
|
handle_reading_status(
|
||||||
|
request.user,
|
||||||
|
desired_shelf,
|
||||||
|
book,
|
||||||
|
privacy=desired_shelf.privacy
|
||||||
|
)
|
||||||
|
|
||||||
|
return redirect('/')
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@require_POST
|
||||||
|
def unshelve(request):
|
||||||
|
''' put a on a user's shelf '''
|
||||||
|
book = models.Edition.objects.get(id=request.POST['book'])
|
||||||
|
current_shelf = models.Shelf.objects.get(id=request.POST['shelf'])
|
||||||
|
|
||||||
|
handle_unshelve(request.user, book, current_shelf)
|
||||||
|
return redirect(request.headers.get('Referer', '/'))
|
||||||
|
|
||||||
|
|
||||||
|
def handle_unshelve(user, book, shelf):
|
||||||
|
''' unshelve a book '''
|
||||||
|
row = models.ShelfBook.objects.get(book=book, shelf=shelf)
|
||||||
|
activity = row.to_remove_activity(user)
|
||||||
|
row.delete()
|
||||||
|
|
||||||
|
broadcast(user, activity)
|
|
@ -3,40 +3,21 @@ import re
|
||||||
|
|
||||||
from django.contrib.postgres.search import TrigramSimilarity
|
from django.contrib.postgres.search import TrigramSimilarity
|
||||||
from django.db.models.functions import Greatest
|
from django.db.models.functions import Greatest
|
||||||
from django.http import HttpResponseNotFound, JsonResponse
|
from django.http import JsonResponse
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
|
||||||
from django.views.decorators.http import require_GET
|
from django.views.decorators.http import require_GET
|
||||||
|
|
||||||
from bookwyrm import outgoing
|
from bookwyrm import outgoing
|
||||||
from bookwyrm import models
|
from bookwyrm import models
|
||||||
from bookwyrm.activitypub import ActivitypubResponse
|
|
||||||
from bookwyrm.connectors import connector_manager
|
from bookwyrm.connectors import connector_manager
|
||||||
from bookwyrm.utils import regex
|
from bookwyrm.utils import regex
|
||||||
|
|
||||||
def get_edition(book_id):
|
|
||||||
''' look up a book in the db and return an edition '''
|
|
||||||
book = models.Book.objects.select_subclasses().get(id=book_id)
|
|
||||||
if isinstance(book, models.Work):
|
|
||||||
book = book.get_default_edition()
|
|
||||||
return book
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_from_username(username):
|
|
||||||
''' helper function to resolve a localname or a username to a user '''
|
|
||||||
# raises DoesNotExist if user is now found
|
|
||||||
try:
|
|
||||||
return models.User.objects.get(localname=username)
|
|
||||||
except models.User.DoesNotExist:
|
|
||||||
return models.User.objects.get(username=username)
|
|
||||||
|
|
||||||
|
|
||||||
def is_api_request(request):
|
def is_api_request(request):
|
||||||
''' check whether a request is asking for html or data '''
|
''' check whether a request is asking for html or data '''
|
||||||
return 'json' in request.headers.get('Accept') or \
|
return 'json' in request.headers.get('Accept') or \
|
||||||
request.path[-5:] == '.json'
|
request.path[-5:] == '.json'
|
||||||
|
|
||||||
|
|
||||||
def server_error_page(request):
|
def server_error_page(request):
|
||||||
''' 500 errors '''
|
''' 500 errors '''
|
||||||
return TemplateResponse(
|
return TemplateResponse(
|
||||||
|
@ -84,59 +65,3 @@ def search(request):
|
||||||
'query': query,
|
'query': query,
|
||||||
}
|
}
|
||||||
return TemplateResponse(request, 'search_results.html', data)
|
return TemplateResponse(request, 'search_results.html', data)
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
|
||||||
@require_GET
|
|
||||||
def user_shelves_page(request, username):
|
|
||||||
''' list of followers '''
|
|
||||||
return shelf_page(request, username, None)
|
|
||||||
|
|
||||||
|
|
||||||
@require_GET
|
|
||||||
def shelf_page(request, username, shelf_identifier):
|
|
||||||
''' display a shelf '''
|
|
||||||
try:
|
|
||||||
user = get_user_from_username(username)
|
|
||||||
except models.User.DoesNotExist:
|
|
||||||
return HttpResponseNotFound()
|
|
||||||
|
|
||||||
if shelf_identifier:
|
|
||||||
shelf = user.shelf_set.get(identifier=shelf_identifier)
|
|
||||||
else:
|
|
||||||
shelf = user.shelf_set.first()
|
|
||||||
|
|
||||||
is_self = request.user == user
|
|
||||||
|
|
||||||
shelves = user.shelf_set
|
|
||||||
if not is_self:
|
|
||||||
follower = user.followers.filter(id=request.user.id).exists()
|
|
||||||
# make sure the user has permission to view the shelf
|
|
||||||
if shelf.privacy == 'direct' or \
|
|
||||||
(shelf.privacy == 'followers' and not follower):
|
|
||||||
return HttpResponseNotFound()
|
|
||||||
|
|
||||||
# only show other shelves that should be visible
|
|
||||||
if follower:
|
|
||||||
shelves = shelves.filter(privacy__in=['public', 'followers'])
|
|
||||||
else:
|
|
||||||
shelves = shelves.filter(privacy='public')
|
|
||||||
|
|
||||||
|
|
||||||
if is_api_request(request):
|
|
||||||
return ActivitypubResponse(shelf.to_activity(**request.GET))
|
|
||||||
|
|
||||||
books = models.ShelfBook.objects.filter(
|
|
||||||
added_by=user, shelf=shelf
|
|
||||||
).order_by('-updated_date').all()
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'title': '%s\'s %s shelf' % (user.display_name, shelf.name),
|
|
||||||
'user': user,
|
|
||||||
'is_self': is_self,
|
|
||||||
'shelves': shelves.all(),
|
|
||||||
'shelf': shelf,
|
|
||||||
'books': [b.book for b in books],
|
|
||||||
}
|
|
||||||
|
|
||||||
return TemplateResponse(request, 'shelf.html', data)
|
|
||||||
|
|
Loading…
Reference in a new issue