forked from mirrors/bookwyrm
Adds book class view and re-works pagination
This commit is contained in:
parent
cf2b9937c6
commit
56e3e98bc1
16 changed files with 197 additions and 439 deletions
|
@ -223,7 +223,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block">
|
||||
<div class="block" id="reviews">
|
||||
{% for review in reviews %}
|
||||
<div class="block">
|
||||
{% include 'snippets/status.html' with status=review hide_book=True depth=1 %}
|
||||
|
@ -231,25 +231,28 @@
|
|||
{% endfor %}
|
||||
|
||||
<div class="block is-flex is-flex-wrap-wrap">
|
||||
{% for rating in ratings %}
|
||||
<div class="block mr-5">
|
||||
<div class="media">
|
||||
<div class="media-left">{% include 'snippets/avatar.html' with user=rating.user %}</div>
|
||||
<div class="media-content">
|
||||
<div>
|
||||
{% include 'snippets/username.html' with user=rating.user %}
|
||||
</div>
|
||||
<div class="field is-grouped mb-0">
|
||||
<div>rated it</div>
|
||||
{% include 'snippets/stars.html' with rating=rating.rating %}
|
||||
</div>
|
||||
<div>
|
||||
<a href="{{ rating.remote_id }}">{{ rating.published_date | naturaltime }}</a>
|
||||
{% for rating in ratings %}
|
||||
<div class="block mr-5">
|
||||
<div class="media">
|
||||
<div class="media-left">{% include 'snippets/avatar.html' with user=rating.user %}</div>
|
||||
<div class="media-content">
|
||||
<div>
|
||||
{% include 'snippets/username.html' with user=rating.user %}
|
||||
</div>
|
||||
<div class="field is-grouped mb-0">
|
||||
<div>rated it</div>
|
||||
{% include 'snippets/stars.html' with rating=rating.rating %}
|
||||
</div>
|
||||
<div>
|
||||
<a href="{{ rating.remote_id }}">{{ rating.published_date | naturaltime }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="block">
|
||||
{% include 'snippets/pagination.html' with page=reviews path=book.local_path anchor="#reviews" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -13,25 +13,7 @@
|
|||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<nav class="pagination" role="navigation" aria-label="pagination">
|
||||
{% if prev %}
|
||||
<p class="pagination-previous">
|
||||
<a href="{{ prev }}">
|
||||
<span class="icon icon-arrow-left"></span>
|
||||
Previous
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if next %}
|
||||
<p class="pagination-next">
|
||||
<a href="{{ next }}">
|
||||
Next
|
||||
<span class="icon icon-arrow-right"></span>
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
</nav>
|
||||
{% include 'snippets/pagination.html' with page=activities path="direct-messages" %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form class="block" name="edit-book" action="/edit-book/{{ book.id }}" method="post" enctype="multipart/form-data">
|
||||
<form class="block" name="edit-book" action="{{ book.local_path }}/edit" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="last_edited_by" value="{{ request.user.id }}">
|
||||
<div class="columns">
|
||||
|
|
|
@ -94,25 +94,7 @@
|
|||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<nav class="pagination" role="navigation" aria-label="pagination">
|
||||
{% if prev %}
|
||||
<p class="pagination-previous">
|
||||
<a href="{{ prev }}">
|
||||
<span class="icon icon-arrow-left"></span>
|
||||
Previous
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if next %}
|
||||
<p class="pagination-next">
|
||||
<a href="{{ next }}">
|
||||
Next
|
||||
<span class="icon icon-arrow-right"></span>
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
</nav>
|
||||
{% include 'snippets/pagination.html' with page=activities path='/'|add:tab anchor="#feed" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
19
bookwyrm/templates/snippets/pagination.html
Normal file
19
bookwyrm/templates/snippets/pagination.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
<nav class="pagination" role="navigation" aria-label="pagination">
|
||||
{% if page.has_previous %}
|
||||
<p class="pagination-previous">
|
||||
<a href="{{ path }}?page={{ page.previous_page_number }}{{ anchor }}">
|
||||
<span class="icon icon-arrow-left"></span>
|
||||
Previous
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if page.has_next %}
|
||||
<p class="pagination-next">
|
||||
<a href="{{ path }}?page={{ page.next_page_number }}{{ anchor }}">
|
||||
Next
|
||||
<span class="icon icon-arrow-right"></span>
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
</nav>
|
|
@ -45,7 +45,7 @@
|
|||
<h2 class="title">User Activity</h2>
|
||||
</div>
|
||||
{% for activity in activities %}
|
||||
<div class="block">
|
||||
<div class="block" id="feed">
|
||||
{% include 'snippets/status.html' with status=activity %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
@ -55,25 +55,7 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<nav class="pagination" role="navigation" aria-label="pagination">
|
||||
{% if prev %}
|
||||
<p class="pagination-previous">
|
||||
<a href="{{ prev }}">
|
||||
<span class="icon icon-arrow-left"></span>
|
||||
Previous
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if next %}
|
||||
<p class="pagination-next">
|
||||
<a href="{{ next }}">
|
||||
Next
|
||||
<span class="icon icon-arrow-right"></span>
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
</nav>
|
||||
{% include 'snippets/pagination.html' with page=activities path=user.local_path anchor="#feed" %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
127
bookwyrm/tests/views/test_book.py
Normal file
127
bookwyrm/tests/views/test_book.py
Normal file
|
@ -0,0 +1,127 @@
|
|||
''' test for app action functionality '''
|
||||
from unittest.mock import patch
|
||||
from django.contrib.auth.models import Group, Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.template.response import TemplateResponse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from bookwyrm import forms, models, views
|
||||
from bookwyrm.activitypub import ActivitypubResponse
|
||||
|
||||
|
||||
class InteractionViews(TestCase):
|
||||
''' viewing and creating statuses '''
|
||||
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.group = Group.objects.create(name='editor')
|
||||
self.group.permissions.add(
|
||||
Permission.objects.create(
|
||||
name='edit_book',
|
||||
codename='edit_book',
|
||||
content_type=ContentType.objects.get_for_model(models.User)).id
|
||||
)
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
def test_book_page(self):
|
||||
''' there are so many views, this just makes sure it LOADS '''
|
||||
view = views.Book.as_view()
|
||||
request = self.factory.get('')
|
||||
request.user = self.local_user
|
||||
with patch('bookwyrm.views.books.is_api_request') as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.book.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
self.assertEqual(result.template_name, 'book.html')
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
request = self.factory.get('')
|
||||
with patch('bookwyrm.views.books.is_api_request') as is_api:
|
||||
is_api.return_value = True
|
||||
result = view(request, self.book.id)
|
||||
self.assertIsInstance(result, ActivitypubResponse)
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
|
||||
def test_edit_book_page(self):
|
||||
''' there are so many views, this just makes sure it LOADS '''
|
||||
view = views.EditBook.as_view()
|
||||
request = self.factory.get('')
|
||||
request.user = self.local_user
|
||||
request.user.is_superuser = True
|
||||
result = view(request, self.book.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
self.assertEqual(result.template_name, 'edit_book.html')
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
|
||||
def test_edit_book(self):
|
||||
''' lets a user edit a book '''
|
||||
view = views.EditBook.as_view()
|
||||
self.local_user.groups.add(self.group)
|
||||
form = forms.EditionForm(instance=self.book)
|
||||
form.data['title'] = 'New Title'
|
||||
form.data['last_edited_by'] = self.local_user.id
|
||||
request = self.factory.post('', form.data)
|
||||
request.user = self.local_user
|
||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
||||
view(request, self.book.id)
|
||||
self.book.refresh_from_db()
|
||||
self.assertEqual(self.book.title, 'New Title')
|
||||
|
||||
|
||||
def test_switch_edition(self):
|
||||
''' updates user's relationships to a book '''
|
||||
work = models.Work.objects.create(title='test work')
|
||||
edition1 = models.Edition.objects.create(
|
||||
title='first ed', parent_work=work)
|
||||
edition2 = models.Edition.objects.create(
|
||||
title='second ed', parent_work=work)
|
||||
shelf = models.Shelf.objects.create(
|
||||
name='Test Shelf', user=self.local_user)
|
||||
shelf.books.add(edition1)
|
||||
models.ReadThrough.objects.create(
|
||||
user=self.local_user, book=edition1)
|
||||
|
||||
self.assertEqual(models.ShelfBook.objects.get().book, edition1)
|
||||
self.assertEqual(models.ReadThrough.objects.get().book, edition1)
|
||||
request = self.factory.post('', {
|
||||
'edition': edition2.id
|
||||
})
|
||||
request.user = self.local_user
|
||||
with patch('bookwyrm.broadcast.broadcast_task.delay'):
|
||||
views.switch_edition(request)
|
||||
|
||||
self.assertEqual(models.ShelfBook.objects.get().book, edition2)
|
||||
self.assertEqual(models.ReadThrough.objects.get().book, edition2)
|
||||
|
||||
|
||||
def test_editions_page(self):
|
||||
''' there are so many views, this just makes sure it LOADS '''
|
||||
view = views.Editions.as_view()
|
||||
request = self.factory.get('')
|
||||
with patch('bookwyrm.views.books.is_api_request') as is_api:
|
||||
is_api.return_value = False
|
||||
result = view(request, self.work.id)
|
||||
self.assertIsInstance(result, TemplateResponse)
|
||||
self.assertEqual(result.template_name, 'editions.html')
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
request = self.factory.get('')
|
||||
with patch('bookwyrm.views.books.is_api_request') as is_api:
|
||||
is_api.return_value = True
|
||||
result = view(request, self.work.id)
|
||||
self.assertIsInstance(result, ActivitypubResponse)
|
||||
self.assertEqual(result.status_code, 200)
|
|
@ -88,10 +88,16 @@ urlpatterns = [
|
|||
re_path(r'^unboost/(?P<status_id>\d+)/?$', views.Unboost.as_view()),
|
||||
|
||||
# books
|
||||
re_path(r'%s(.json)?/?$' % book_path, vviews.book_page),
|
||||
re_path(r'%s/edit/?$' % book_path, vviews.edit_book_page),
|
||||
re_path(r'%s(.json)?/?$' % book_path, views.Book.as_view()),
|
||||
re_path(r'%s/edit/?$' % book_path, views.EditBook.as_view()),
|
||||
re_path(r'%s/editions(.json)?/?$' % book_path, views.Editions.as_view()),
|
||||
re_path(r'^upload-cover/(?P<book_id>\d+)/?$', views.upload_cover),
|
||||
re_path(r'^add-description/(?P<book_id>\d+)/?$', views.add_description),
|
||||
re_path(r'^resolve-book/?$', views.resolve_book),
|
||||
re_path(r'^switch-edition/?$', views.switch_edition),
|
||||
|
||||
re_path(r'^author/(?P<author_id>[\w\-]+)/edit/?$', vviews.edit_author_page),
|
||||
re_path(r'%s/editions(.json)?/?$' % book_path, vviews.editions_page),
|
||||
|
||||
re_path(r'^tag/?$', actions.tag),
|
||||
re_path(r'^untag/?$', actions.untag),
|
||||
|
||||
|
@ -105,15 +111,8 @@ urlpatterns = [
|
|||
|
||||
re_path(r'^search/?$', vviews.search),
|
||||
|
||||
# internal action endpoints
|
||||
|
||||
re_path(r'^resolve-book/?$', actions.resolve_book),
|
||||
re_path(r'^edit-book/(?P<book_id>\d+)/?$', actions.edit_book),
|
||||
re_path(r'^upload-cover/(?P<book_id>\d+)/?$', actions.upload_cover),
|
||||
re_path(r'^add-description/(?P<book_id>\d+)/?$', actions.add_description),
|
||||
re_path(r'^edit-author/(?P<author_id>\d+)/?$', actions.edit_author),
|
||||
|
||||
re_path(r'^switch-edition/?$', actions.switch_edition),
|
||||
re_path(r'^edit-readthrough/?$', actions.edit_readthrough),
|
||||
re_path(r'^delete-readthrough/?$', actions.delete_readthrough),
|
||||
re_path(r'^create-readthrough/?$', actions.create_readthrough),
|
||||
|
|
|
@ -3,7 +3,6 @@ import dateutil.parser
|
|||
from dateutil.parser import ParserError
|
||||
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.db import transaction
|
||||
from django.http import HttpResponseBadRequest, HttpResponseNotFound
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template.response import TemplateResponse
|
||||
|
@ -11,108 +10,9 @@ from django.utils import timezone
|
|||
from django.views.decorators.http import require_POST
|
||||
|
||||
from bookwyrm import forms, models, outgoing
|
||||
from bookwyrm.connectors import connector_manager
|
||||
from bookwyrm.broadcast import broadcast
|
||||
from bookwyrm.vviews import get_user_from_username, get_edition
|
||||
|
||||
|
||||
@require_POST
|
||||
def resolve_book(request):
|
||||
''' figure out the local path to a book from a remote_id '''
|
||||
remote_id = request.POST.get('remote_id')
|
||||
connector = connector_manager.get_or_create_connector(remote_id)
|
||||
book = connector.get_or_create_book(remote_id)
|
||||
|
||||
return redirect('/book/%d' % book.id)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('bookwyrm.edit_book', raise_exception=True)
|
||||
@require_POST
|
||||
def edit_book(request, book_id):
|
||||
''' edit a book cool '''
|
||||
book = get_object_or_404(models.Edition, id=book_id)
|
||||
|
||||
form = forms.EditionForm(request.POST, request.FILES, instance=book)
|
||||
if not form.is_valid():
|
||||
data = {
|
||||
'title': 'Edit Book',
|
||||
'book': book,
|
||||
'form': form
|
||||
}
|
||||
return TemplateResponse(request, 'edit_book.html', data)
|
||||
book = form.save()
|
||||
|
||||
broadcast(request.user, book.to_update_activity(request.user))
|
||||
return redirect('/book/%s' % book.id)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
@transaction.atomic
|
||||
def switch_edition(request):
|
||||
''' switch your copy of a book to a different edition '''
|
||||
edition_id = request.POST.get('edition')
|
||||
new_edition = get_object_or_404(models.Edition, id=edition_id)
|
||||
shelfbooks = models.ShelfBook.objects.filter(
|
||||
book__parent_work=new_edition.parent_work,
|
||||
shelf__user=request.user
|
||||
)
|
||||
for shelfbook in shelfbooks.all():
|
||||
broadcast(request.user, shelfbook.to_remove_activity(request.user))
|
||||
|
||||
shelfbook.book = new_edition
|
||||
shelfbook.save()
|
||||
|
||||
broadcast(request.user, shelfbook.to_add_activity(request.user))
|
||||
|
||||
readthroughs = models.ReadThrough.objects.filter(
|
||||
book__parent_work=new_edition.parent_work,
|
||||
user=request.user
|
||||
)
|
||||
for readthrough in readthroughs.all():
|
||||
readthrough.book = new_edition
|
||||
readthrough.save()
|
||||
|
||||
return redirect('/book/%d' % new_edition.id)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def upload_cover(request, book_id):
|
||||
''' upload a new cover '''
|
||||
book = get_object_or_404(models.Edition, id=book_id)
|
||||
|
||||
form = forms.CoverForm(request.POST, request.FILES, instance=book)
|
||||
if not form.is_valid():
|
||||
return redirect('/book/%d' % book.id)
|
||||
|
||||
book.cover = form.files['cover']
|
||||
book.save()
|
||||
|
||||
broadcast(request.user, book.to_update_activity(request.user))
|
||||
return redirect('/book/%s' % book.id)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
@permission_required('bookwyrm.edit_book', raise_exception=True)
|
||||
def add_description(request, book_id):
|
||||
''' upload a new cover '''
|
||||
if not request.method == 'POST':
|
||||
return redirect('/')
|
||||
|
||||
book = get_object_or_404(models.Edition, id=book_id)
|
||||
|
||||
description = request.POST.get('description')
|
||||
|
||||
book.description = description
|
||||
book.save()
|
||||
|
||||
broadcast(request.user, book.to_update_activity(request.user))
|
||||
return redirect('/book/%s' % book.id)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('bookwyrm.edit_book', raise_exception=True)
|
||||
@require_POST
|
||||
|
@ -378,33 +278,6 @@ def untag(request):
|
|||
return redirect('/book/%s' % book_id)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def unfavorite(request, status_id):
|
||||
''' like a status '''
|
||||
status = models.Status.objects.get(id=status_id)
|
||||
outgoing.handle_unfavorite(request.user, status)
|
||||
return redirect(request.headers.get('Referer', '/'))
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def boost(request, status_id):
|
||||
''' boost a status '''
|
||||
status = models.Status.objects.get(id=status_id)
|
||||
outgoing.handle_boost(request.user, status)
|
||||
return redirect(request.headers.get('Referer', '/'))
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def unboost(request, status_id):
|
||||
''' boost a status '''
|
||||
status = models.Status.objects.get(id=status_id)
|
||||
outgoing.handle_unboost(request.user, status)
|
||||
return redirect(request.headers.get('Referer', '/'))
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def follow(request):
|
||||
|
|
|
@ -9,3 +9,5 @@ from .import_data import Import, ImportStatus
|
|||
from .user import User, EditUser, Followers, Following
|
||||
from .status import Status, Replies, CreateStatus, DeleteStatus
|
||||
from .interaction import Favorite, Unfavorite, Boost, Unboost
|
||||
from .books import Book, EditBook, Editions
|
||||
from .books import upload_cover, add_description, switch_edition, resolve_book
|
||||
|
|
|
@ -18,19 +18,9 @@ class DirectMessage(View):
|
|||
activities = get_activity_feed(request.user, 'direct')
|
||||
paginated = Paginator(activities, PAGE_LENGTH)
|
||||
activity_page = paginated.page(page)
|
||||
|
||||
prev_page = next_page = None
|
||||
if activity_page.has_next():
|
||||
next_page = '/direct-message/?page=%d#feed' % \
|
||||
activity_page.next_page_number()
|
||||
if activity_page.has_previous():
|
||||
prev_page = '/direct-messages/?page=%d#feed' % \
|
||||
activity_page.previous_page_number()
|
||||
data = {
|
||||
'title': 'Direct Messages',
|
||||
'user': request.user,
|
||||
'activities': activity_page.object_list,
|
||||
'next': next_page,
|
||||
'prev': prev_page,
|
||||
'activities': activity_page,
|
||||
}
|
||||
return TemplateResponse(request, 'direct_messages.html', data)
|
||||
|
|
|
@ -137,3 +137,13 @@ def handle_remote_webfinger(query):
|
|||
except KeyError:
|
||||
return None
|
||||
return user
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
|
|
@ -105,7 +105,6 @@ class Boost(View):
|
|||
return redirect(request.headers.get('Referer', '/'))
|
||||
|
||||
|
||||
|
||||
@method_decorator(login_required, name='dispatch')
|
||||
class Unboost(View):
|
||||
''' boost a status '''
|
||||
|
|
|
@ -86,23 +86,12 @@ class Feed(View):
|
|||
activities = get_activity_feed(
|
||||
request.user, ['public', 'followers'])
|
||||
paginated = Paginator(activities, PAGE_LENGTH)
|
||||
activity_page = paginated.page(page)
|
||||
|
||||
prev_page = next_page = None
|
||||
if activity_page.has_next():
|
||||
next_page = '/%s/?page=%d#feed' % \
|
||||
(tab, activity_page.next_page_number())
|
||||
if activity_page.has_previous():
|
||||
prev_page = '/%s/?page=%d#feed' % \
|
||||
(tab, activity_page.previous_page_number())
|
||||
data = {
|
||||
'title': 'Updates Feed',
|
||||
'user': request.user,
|
||||
'suggested_books': suggested_books,
|
||||
'activities': activity_page.object_list,
|
||||
'activities': paginated.page(page),
|
||||
'tab': tab,
|
||||
'next': next_page,
|
||||
'prev': prev_page,
|
||||
}
|
||||
return TemplateResponse(request, 'feed.html', data)
|
||||
|
||||
|
|
|
@ -70,24 +70,13 @@ class User(View):
|
|||
queryset=models.Status.objects.filter(user=user)
|
||||
)
|
||||
paginated = Paginator(activities, PAGE_LENGTH)
|
||||
activity_page = paginated.page(page)
|
||||
|
||||
prev_page = next_page = None
|
||||
if activity_page.has_next():
|
||||
next_page = '/user/%s/?page=%d' % \
|
||||
(username, activity_page.next_page_number())
|
||||
if activity_page.has_previous():
|
||||
prev_page = '/user/%s/?page=%d' % \
|
||||
(username, activity_page.previous_page_number())
|
||||
data = {
|
||||
'title': user.name,
|
||||
'user': user,
|
||||
'is_self': is_self,
|
||||
'shelves': shelf_preview,
|
||||
'shelf_count': shelves.count(),
|
||||
'activities': activity_page.object_list,
|
||||
'next': next_page,
|
||||
'prev': prev_page,
|
||||
'activities': paginated.page(page),
|
||||
}
|
||||
|
||||
return TemplateResponse(request, 'user.html', data)
|
||||
|
|
|
@ -3,8 +3,7 @@ import re
|
|||
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.contrib.postgres.search import TrigramSimilarity
|
||||
from django.core.paginator import Paginator
|
||||
from django.db.models import Avg, Q
|
||||
from django.db.models import Q
|
||||
from django.db.models.functions import Greatest
|
||||
from django.http import HttpResponseNotFound, JsonResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
@ -16,10 +15,8 @@ from bookwyrm import outgoing
|
|||
from bookwyrm import forms, models
|
||||
from bookwyrm.activitypub import ActivitypubResponse
|
||||
from bookwyrm.connectors import connector_manager
|
||||
from bookwyrm.settings import PAGE_LENGTH
|
||||
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)
|
||||
|
@ -27,6 +24,8 @@ def get_edition(book_id):
|
|||
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
|
||||
|
@ -41,14 +40,6 @@ def is_api_request(request):
|
|||
return 'json' in request.headers.get('Accept') or \
|
||||
request.path[-5:] == '.json'
|
||||
|
||||
def is_bookworm_request(request):
|
||||
''' check if the request is coming from another bookworm instance '''
|
||||
user_agent = request.headers.get('User-Agent')
|
||||
if user_agent is None or \
|
||||
re.search(regex.bookwyrm_user_agent, user_agent) is None:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def server_error_page(request):
|
||||
''' 500 errors '''
|
||||
|
@ -62,64 +53,6 @@ def not_found_page(request, _):
|
|||
request, 'notfound.html', {'title': 'Not found'}, status=404)
|
||||
|
||||
|
||||
def get_activity_feed(
|
||||
user, privacy, local_only=False, following_only=False,
|
||||
queryset=models.Status.objects):
|
||||
''' get a filtered queryset of statuses '''
|
||||
privacy = privacy if isinstance(privacy, list) else [privacy]
|
||||
# if we're looking at Status, we need this. We don't if it's Comment
|
||||
if hasattr(queryset, 'select_subclasses'):
|
||||
queryset = queryset.select_subclasses()
|
||||
|
||||
# exclude deleted
|
||||
queryset = queryset.exclude(deleted=True).order_by('-published_date')
|
||||
|
||||
# you can't see followers only or direct messages if you're not logged in
|
||||
if user.is_anonymous:
|
||||
privacy = [p for p in privacy if not p in ['followers', 'direct']]
|
||||
|
||||
# filter to only privided privacy levels
|
||||
queryset = queryset.filter(privacy__in=privacy)
|
||||
|
||||
# only include statuses the user follows
|
||||
if following_only:
|
||||
queryset = queryset.exclude(
|
||||
~Q(# remove everythign except
|
||||
Q(user__in=user.following.all()) | # user follwoing
|
||||
Q(user=user) |# is self
|
||||
Q(mention_users=user)# mentions user
|
||||
),
|
||||
)
|
||||
# exclude followers-only statuses the user doesn't follow
|
||||
elif 'followers' in privacy:
|
||||
queryset = queryset.exclude(
|
||||
~Q(# user isn't following and it isn't their own status
|
||||
Q(user__in=user.following.all()) | Q(user=user)
|
||||
),
|
||||
privacy='followers' # and the status is followers only
|
||||
)
|
||||
|
||||
# exclude direct messages not intended for the user
|
||||
if 'direct' in privacy:
|
||||
queryset = queryset.exclude(
|
||||
~Q(
|
||||
Q(user=user) | Q(mention_users=user)
|
||||
), privacy='direct'
|
||||
)
|
||||
|
||||
# filter for only local status
|
||||
if local_only:
|
||||
queryset = queryset.filter(user__local=True)
|
||||
|
||||
# remove statuses that have boosts in the same queryset
|
||||
try:
|
||||
queryset = queryset.filter(~Q(boosters__in=queryset))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
@require_GET
|
||||
def search(request):
|
||||
''' that search bar up top '''
|
||||
|
@ -157,111 +90,6 @@ def search(request):
|
|||
return TemplateResponse(request, 'search_results.html', data)
|
||||
|
||||
|
||||
@require_GET
|
||||
def book_page(request, book_id):
|
||||
''' info about a book '''
|
||||
try:
|
||||
page = int(request.GET.get('page', 1))
|
||||
except ValueError:
|
||||
page = 1
|
||||
|
||||
try:
|
||||
book = models.Book.objects.select_subclasses().get(id=book_id)
|
||||
except models.Book.DoesNotExist:
|
||||
return HttpResponseNotFound()
|
||||
|
||||
if is_api_request(request):
|
||||
return ActivitypubResponse(book.to_activity())
|
||||
|
||||
if isinstance(book, models.Work):
|
||||
book = book.get_default_edition()
|
||||
if not book:
|
||||
return HttpResponseNotFound()
|
||||
|
||||
work = book.parent_work
|
||||
if not work:
|
||||
return HttpResponseNotFound()
|
||||
|
||||
reviews = models.Review.objects.filter(
|
||||
book__in=work.editions.all(),
|
||||
)
|
||||
# all reviews for the book
|
||||
reviews = get_activity_feed(
|
||||
request.user,
|
||||
['public', 'unlisted', 'followers', 'direct'],
|
||||
queryset=reviews
|
||||
)
|
||||
|
||||
# the reviews to show
|
||||
paginated = Paginator(reviews.exclude(
|
||||
Q(content__isnull=True) | Q(content='')
|
||||
), PAGE_LENGTH)
|
||||
reviews_page = paginated.page(page)
|
||||
|
||||
prev_page = next_page = None
|
||||
if reviews_page.has_next():
|
||||
next_page = '/book/%d/?page=%d' % \
|
||||
(book_id, reviews_page.next_page_number())
|
||||
if reviews_page.has_previous():
|
||||
prev_page = '/book/%s/?page=%d' % \
|
||||
(book_id, reviews_page.previous_page_number())
|
||||
|
||||
user_tags = readthroughs = user_shelves = other_edition_shelves = []
|
||||
if request.user.is_authenticated:
|
||||
user_tags = models.UserTag.objects.filter(
|
||||
book=book, user=request.user
|
||||
).values_list('tag__identifier', flat=True)
|
||||
|
||||
readthroughs = models.ReadThrough.objects.filter(
|
||||
user=request.user,
|
||||
book=book,
|
||||
).order_by('start_date')
|
||||
|
||||
user_shelves = models.ShelfBook.objects.filter(
|
||||
added_by=request.user, book=book
|
||||
)
|
||||
|
||||
other_edition_shelves = models.ShelfBook.objects.filter(
|
||||
~Q(book=book),
|
||||
added_by=request.user,
|
||||
book__parent_work=book.parent_work,
|
||||
)
|
||||
|
||||
data = {
|
||||
'title': book.title,
|
||||
'book': book,
|
||||
'reviews': reviews_page,
|
||||
'review_count': reviews.count(),
|
||||
'ratings': reviews.filter(Q(content__isnull=True) | Q(content='')),
|
||||
'rating': reviews.aggregate(Avg('rating'))['rating__avg'],
|
||||
'tags': models.UserTag.objects.filter(book=book),
|
||||
'user_tags': user_tags,
|
||||
'user_shelves': user_shelves,
|
||||
'other_edition_shelves': other_edition_shelves,
|
||||
'readthroughs': readthroughs,
|
||||
'path': '/book/%s' % book_id,
|
||||
'next': next_page,
|
||||
'prev': prev_page,
|
||||
}
|
||||
return TemplateResponse(request, 'book.html', data)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('bookwyrm.edit_book', raise_exception=True)
|
||||
@require_GET
|
||||
def edit_book_page(request, book_id):
|
||||
''' info about a book '''
|
||||
book = get_edition(book_id)
|
||||
if not book.description:
|
||||
book.description = book.parent_work.description
|
||||
data = {
|
||||
'title': 'Edit Book',
|
||||
'book': book,
|
||||
'form': forms.EditionForm(instance=book)
|
||||
}
|
||||
return TemplateResponse(request, 'edit_book.html', data)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('bookwyrm.edit_book', raise_exception=True)
|
||||
@require_GET
|
||||
|
@ -276,22 +104,6 @@ def edit_author_page(request, author_id):
|
|||
return TemplateResponse(request, 'edit_author.html', data)
|
||||
|
||||
|
||||
@require_GET
|
||||
def editions_page(request, book_id):
|
||||
''' list of editions of a book '''
|
||||
work = get_object_or_404(models.Work, id=book_id)
|
||||
|
||||
if is_api_request(request):
|
||||
return ActivitypubResponse(work.to_edition_list(**request.GET))
|
||||
|
||||
data = {
|
||||
'title': 'Editions of %s' % work.title,
|
||||
'editions': work.editions.order_by('-edition_rank').all(),
|
||||
'work': work,
|
||||
}
|
||||
return TemplateResponse(request, 'editions.html', data)
|
||||
|
||||
|
||||
@require_GET
|
||||
def author_page(request, author_id):
|
||||
''' landing page for an author '''
|
||||
|
|
Loading…
Reference in a new issue