Merge pull request #567 from mouse-reeve/organize-templates

Organize templates
This commit is contained in:
Mouse Reeve 2021-01-29 11:14:47 -08:00 committed by GitHub
commit 94974d9f73
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 430 additions and 373 deletions

View file

@ -35,8 +35,17 @@ window.onload = function() {
// polling
document.querySelectorAll('[data-poll]')
.forEach(el => polling(el));
// browser back behavior
document.querySelectorAll('[data-back]')
.forEach(t => t.onclick = back);
};
function back(e) {
e.preventDefault();
history.back();
}
function polling(el) {
let delay = 10000 + (Math.random() * 1000);
setTimeout(function() {

View file

@ -1,5 +1,5 @@
{% extends 'layout.html' %}
{% block content %}
{% extends 'feed/feed_layout.html' %}
{% block panel %}
<div class="block">
<h1 class="title">Direct Messages</h1>

View file

@ -0,0 +1,39 @@
{% extends 'feed/feed_layout.html' %}
{% load bookwyrm_tags %}
{% block panel %}
<h1 class="title">{{ tab | title }} Timeline</h1>
<div class="tabs">
<ul>
<li class="{% if tab == 'home' %}is-active{% endif %}">
<a href="/#feed">Home</a>
</li>
<li class="{% if tab == 'local' %}is-active{% endif %}">
<a href="/local#feed">Local</a>
</li>
<li class="{% if tab == 'federated' %}is-active{% endif %}">
<a href="/federated#feed">Federated</a>
</li>
</ul>
</div>
{# announcements and system messages #}
{% if not goal and tab == 'home' %}
{% now 'Y' as year %}
<section class="block hidden" aria-title="Announcements" data-hide="hide-{{ year }}-reading-goal">
{% include 'snippets/goal_card.html' with year=year %}
<hr>
</section>
{% endif %}
{# activity feed #}
{% if not activities %}
<p>There aren't any activities right now! Try following a user to get started</p>
{% endif %}
{% for activity in activities %}
<div class="block">
{% include 'snippets/status.html' with status=activity %}
</div>
{% endfor %}
{% endblock %}

View file

@ -3,6 +3,7 @@
{% block content %}
<div class="columns">
{% if user.is_authenticated %}
<div class="column is-one-third">
<h2 class="title is-5">Your books</h2>
{% if not suggested_books %}
@ -69,43 +70,15 @@
</section>
{% endif %}
</div>
{% endif %}
<div class="column is-two-thirds" id="feed">
<h1 class="title">{{ tab | title }} Timeline</h1>
<div class="tabs">
<ul>
<li class="{% if tab == 'home' %}is-active{% endif %}">
<a href="/#feed">Home</a>
</li>
<li class="{% if tab == 'local' %}is-active{% endif %}">
<a href="/local#feed">Local</a>
</li>
<li class="{% if tab == 'federated' %}is-active{% endif %}">
<a href="/federated#feed">Federated</a>
</li>
</ul>
</div>
{# announcements and system messages #}
{% if not goal and tab == 'home' %}
{% now 'Y' as year %}
<section class="block hidden" aria-title="Announcements" data-hide="hide-{{ year }}-reading-goal">
{% include 'snippets/goal_card.html' with year=year %}
<hr>
</section>
{% endif %}
{# activity feed #}
{% if not activities %}
<p>There aren't any activities right now! Try following a user to get started</p>
{% endif %}
{% for activity in activities %}
<div class="block">
{% include 'snippets/status.html' with status=activity %}
</div>
{% endfor %}
{% block panel %}{% endblock %}
{% if activities %}
{% include 'snippets/pagination.html' with page=activities path='/'|add:tab anchor="#feed" %}
{% endif %}
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,13 @@
{% extends 'feed/feed_layout.html' %}
{% block panel %}
<header class="block">
<a href="/#feed" class="button" data-back>
<span class="icon icon-arrow-left" aira-hidden="true"></span>
<span>Back</span>
</a>
</header>
{% include 'feed/thread.html' with status=status depth=0 max_depth=6 is_root=True direction=0 %}
{% endblock %}

View file

@ -81,7 +81,7 @@
</a>
</li>
<li>
<a href="/edit-profile" class="navbar-item">
<a href="/preferences/profile" class="navbar-item">
Settings
</a>
</li>

View file

@ -1,4 +1,4 @@
{% extends 'preferences_layout.html' %}
{% extends 'preferences/preferences_layout.html' %}
{% block header %}
Blocked Users

View file

@ -1,4 +1,4 @@
{% extends 'preferences_layout.html' %}
{% extends 'preferences/preferences_layout.html' %}
{% block header %}
Change Password
{% endblock %}

View file

@ -1,4 +1,4 @@
{% extends 'preferences_layout.html' %}
{% extends 'preferences/preferences_layout.html' %}
{% block header %}
Edit Profile
{% endblock %}

View file

@ -10,16 +10,16 @@
<h2 class="menu-label">Account</h2>
<ul class="menu-list">
<li>
<a href="/edit-profile"{% if '/edit-profile' in request.path %} class="is-active" aria-selected="true"{% endif %}>Profile</a>
<a href="/preferences/profile"{% if '/preferences/profile' in request.path %} class="is-active" aria-selected="true"{% endif %}>Profile</a>
</li>
<li>
<a href="/change-password"{% if '/change-password' in request.path %} class="is-active" aria-selected="true"{% endif %}>Change password</a>
<a href="/preferences/password"{% if '/preferences/password' in request.path %} class="is-active" aria-selected="true"{% endif %}>Change password</a>
</li>
</ul>
<h2 class="menu-label">Relationships</h2>
<ul class="menu-list">
<li>
<a href="/block"{% if '/block' in request.path %} class="is-active" aria-selected="true"{% endif %}>Blocked users</a>
<a href="/preferences/block"{% if '/preferences/block' in request.path %} class="is-active" aria-selected="true"{% endif %}>Blocked users</a>
</li>
</ul>
</nav>

View file

@ -1,4 +1,4 @@
{% extends 'snippets/components/modal.html' %}
{% extends 'components/modal.html' %}
{% block modal-title %}Delete these read dates?{% endblock %}
{% block modal-body %}
{% if readthrough.progress_updates|length > 0 %}

View file

@ -1,4 +1,4 @@
{% extends 'snippets/components/modal.html' %}
{% extends 'components/modal.html' %}
{% block modal-title %}
Finish "<em>{{ book.title }}</em>"

View file

@ -1,4 +1,4 @@
{% extends 'snippets/components/card.html' %}
{% extends 'components/card.html' %}
{% block card-header %}
<h3 class="card-header-title has-background-primary has-text-white">

View file

@ -1,4 +1,4 @@
{% extends 'snippets/components/dropdown.html' %}
{% extends 'components/dropdown.html' %}
{% block dropdown-trigger %}
<span>Change shelf</span>
<span class="icon icon-arrow-down" aria-hidden="true"></span>

View file

@ -1,4 +1,4 @@
{% extends 'snippets/components/dropdown.html' %}
{% extends 'components/dropdown.html' %}
{% block dropdown-trigger %}
<span class="icon icon-arrow-down">
<span class="is-sr-only">More shelves</span>

View file

@ -1,4 +1,4 @@
{% extends 'snippets/components/modal.html' %}
{% extends 'components/modal.html' %}
{% block modal-title %}
Start "<em>{{ book.title }}</em>"

View file

@ -1,4 +1,4 @@
{% extends 'snippets/components/card.html' %}
{% extends 'components/card.html' %}
{% load bookwyrm_tags %}
{% load humanize %}

View file

@ -1,4 +1,4 @@
{% extends 'snippets/components/dropdown.html' %}
{% extends 'components/dropdown.html' %}
{% load bookwyrm_tags %}
{% block dropdown-trigger %}

View file

@ -1,4 +1,4 @@
{% extends 'snippets/components/dropdown.html' %}
{% extends 'components/dropdown.html' %}
{% load bookwyrm_tags %}
{% block dropdown-trigger %}

View file

@ -1,9 +0,0 @@
{% extends 'layout.html' %}
{% block content %}
<div class="block">
{% include 'snippets/thread.html' with status=status depth=0 max_depth=6 is_root=True direction=0 %}
</div>
{% endblock %}

View file

@ -1,18 +1,17 @@
{% extends 'layout.html' %}
{% extends 'user/user_layout.html' %}
{% load bookwyrm_tags %}
{% block content %}
<div class="block">
<h1 class="title">
{% if is_self %}Your
{% else %}
{% include 'snippets/username.html' with user=user possessive=True %}
{% endif %}
followers
</h1>
</div>
{% include 'snippets/user_header.html' with user=user %}
{% block header %}
<h1 class="title">
{% if is_self %}Your
{% else %}
{% include 'snippets/username.html' with user=user possessive=True %}
{% endif %}
followers
</h1>
{% endblock %}
{% block panel %}
<div class="block">
<h2 class="title">Followers</h2>
{% for followers in followers %}
@ -34,5 +33,4 @@
<div>{{ user|username }} has no followers</div>
{% endif %}
</div>
{% endblock %}

View file

@ -1,18 +1,17 @@
{% extends 'layout.html' %}
{% extends 'user/user_layout.html' %}
{% load bookwyrm_tags %}
{% block content %}
<div class="block">
<h1 class="title">
Users following
{% if is_self %}you
{% else %}
{% include 'snippets/username.html' with user=user %}
{% endif %}
</h1>
</div>
{% include 'snippets/user_header.html' with user=user %}
{% block header %}
<h1 class="title">
Users following
{% if is_self %}you
{% else %}
{% include 'snippets/username.html' with user=user %}
{% endif %}
</h1>
{% endblock %}
{% block panel %}
<div class="block">
<h2 class="title">Following</h2>
{% for follower in user.following.all %}
@ -34,5 +33,4 @@
<div>{{ user|username }} isn't following any users</div>
{% endif %}
</div>
{% endblock %}

View file

@ -1,13 +1,13 @@
{% extends 'layout.html' %}
{% block content %}
{% extends 'user/user_layout.html' %}
{% block header %}
<div class="columns">
<div class="column">
<h1 class="title">User profile</h1>
</div>
{% if is_self %}
<div class="column is-narrow">
<a href="/edit-profile">
<a href="/preferences/profile">
<span class="icon icon-pencil" title="Edit profile">
<span class="is-sr-only">Edit profile</span>
</span>
@ -15,8 +15,9 @@
</div>
{% endif %}
</div>
{% endblock %}
{% include 'snippets/user_header.html' with user=user %}
{% block panel %}
{% if user.bookwyrm_user %}
<div class="block">
<h2 class="title">Shelves</h2>

View file

@ -1,5 +1,13 @@
{% extends 'layout.html' %}
{% load humanize %}
{% load bookwyrm_tags %}
{% block content %}
<header class="block">
{% block header %}{% endblock %}
</header>
{# user bio #}
<div class="block">
<div class="columns">
<div class="column is-narrow">
@ -60,3 +68,6 @@
{% endif %}
</div>
{% block panel %}{% endblock %}
{% endblock %}

View file

@ -32,7 +32,7 @@ class BlockViews(TestCase):
request.user = self.local_user
result = view(request)
self.assertIsInstance(result, TemplateResponse)
self.assertEqual(result.template_name, 'blocks.html')
self.assertEqual(result.template_name, 'preferences/blocks.html')
self.assertEqual(result.status_code, 200)
def test_block_post(self):

View file

@ -1,28 +0,0 @@
''' test for app action functionality '''
from django.template.response import TemplateResponse
from django.test import TestCase
from django.test.client import RequestFactory
from bookwyrm import models
from bookwyrm import views
class DirectMessageViews(TestCase):
''' dms '''
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.mouse', 'password',
local=True, localname='mouse')
def test_direct_messages_page(self):
''' there are so many views, this just makes sure it LOADS '''
view = views.DirectMessage.as_view()
request = self.factory.get('')
request.user = self.local_user
result = view(request)
self.assertIsInstance(result, TemplateResponse)
self.assertEqual(result.template_name, 'direct_messages.html')
self.assertEqual(result.status_code, 200)

View file

@ -0,0 +1,99 @@
''' 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
from bookwyrm import views
from bookwyrm.activitypub import ActivitypubResponse
class FeedMessageViews(TestCase):
''' dms '''
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.mouse', 'password',
local=True, localname='mouse')
self.book = models.Edition.objects.create(
title='Example Edition',
remote_id='https://example.com/book/1',
)
def test_feed(self):
''' there are so many views, this just makes sure it LOADS '''
view = views.Feed.as_view()
request = self.factory.get('')
request.user = self.local_user
result = view(request, 'local')
self.assertIsInstance(result, TemplateResponse)
self.assertEqual(result.template_name, 'feed/feed.html')
self.assertEqual(result.status_code, 200)
def test_status_page(self):
''' there are so many views, this just makes sure it LOADS '''
view = views.Status.as_view()
status = models.Status.objects.create(
content='hi', user=self.local_user)
request = self.factory.get('')
request.user = self.local_user
with patch('bookwyrm.views.feed.is_api_request') as is_api:
is_api.return_value = False
result = view(request, 'mouse', status.id)
self.assertIsInstance(result, TemplateResponse)
self.assertEqual(result.template_name, 'feed/status.html')
self.assertEqual(result.status_code, 200)
with patch('bookwyrm.views.feed.is_api_request') as is_api:
is_api.return_value = True
result = view(request, 'mouse', status.id)
self.assertIsInstance(result, ActivitypubResponse)
self.assertEqual(result.status_code, 200)
def test_replies_page(self):
''' there are so many views, this just makes sure it LOADS '''
view = views.Replies.as_view()
status = models.Status.objects.create(
content='hi', user=self.local_user)
request = self.factory.get('')
request.user = self.local_user
with patch('bookwyrm.views.feed.is_api_request') as is_api:
is_api.return_value = False
result = view(request, 'mouse', status.id)
self.assertIsInstance(result, TemplateResponse)
self.assertEqual(result.template_name, 'feed/status.html')
self.assertEqual(result.status_code, 200)
with patch('bookwyrm.views.feed.is_api_request') as is_api:
is_api.return_value = True
result = view(request, 'mouse', status.id)
self.assertIsInstance(result, ActivitypubResponse)
self.assertEqual(result.status_code, 200)
def test_direct_messages_page(self):
''' there are so many views, this just makes sure it LOADS '''
view = views.DirectMessage.as_view()
request = self.factory.get('')
request.user = self.local_user
result = view(request)
self.assertIsInstance(result, TemplateResponse)
self.assertEqual(result.template_name, 'feed/direct_messages.html')
self.assertEqual(result.status_code, 200)
def test_get_suggested_book(self):
''' gets books the ~*~ algorithm ~*~ thinks you want to post about '''
models.ShelfBook.objects.create(
book=self.book,
added_by=self.local_user,
shelf=self.local_user.shelf_set.get(identifier='reading')
)
suggestions = views.feed.get_suggested_books(self.local_user)
self.assertEqual(suggestions[0]['name'], 'Currently Reading')
self.assertEqual(suggestions[0]['books'][0], self.book)

View file

@ -18,10 +18,6 @@ class LandingViews(TestCase):
local=True, localname='mouse')
self.anonymous_user = AnonymousUser
self.anonymous_user.is_authenticated = False
self.book = models.Edition.objects.create(
title='Example Edition',
remote_id='https://example.com/book/1',
)
def test_home_page(self):
@ -31,7 +27,7 @@ class LandingViews(TestCase):
request.user = self.local_user
result = view(request)
self.assertEqual(result.status_code, 200)
self.assertEqual(result.template_name, 'feed.html')
self.assertEqual(result.template_name, 'feed/feed.html')
request.user = self.anonymous_user
result = view(request)
@ -51,17 +47,6 @@ class LandingViews(TestCase):
self.assertEqual(result.status_code, 200)
def test_feed(self):
''' there are so many views, this just makes sure it LOADS '''
view = views.Feed.as_view()
request = self.factory.get('')
request.user = self.local_user
result = view(request, 'local')
self.assertIsInstance(result, TemplateResponse)
self.assertEqual(result.template_name, 'feed.html')
self.assertEqual(result.status_code, 200)
def test_discover(self):
''' there are so many views, this just makes sure it LOADS '''
view = views.Discover.as_view()
@ -70,15 +55,3 @@ class LandingViews(TestCase):
self.assertIsInstance(result, TemplateResponse)
self.assertEqual(result.template_name, 'discover.html')
self.assertEqual(result.status_code, 200)
def test_get_suggested_book(self):
''' gets books the ~*~ algorithm ~*~ thinks you want to post about '''
models.ShelfBook.objects.create(
book=self.book,
added_by=self.local_user,
shelf=self.local_user.shelf_set.get(identifier='reading')
)
suggestions = views.landing.get_suggested_books(self.local_user)
self.assertEqual(suggestions[0]['name'], 'Currently Reading')
self.assertEqual(suggestions[0]['books'][0], self.book)

View file

@ -106,7 +106,7 @@ class PasswordViews(TestCase):
result = view(request)
self.assertIsInstance(result, TemplateResponse)
self.assertEqual(result.template_name, 'change_password.html')
self.assertEqual(result.template_name, 'preferences/change_password.html')
self.assertEqual(result.status_code, 200)

View file

@ -36,48 +36,6 @@ class StatusViews(TestCase):
)
def test_status_page(self):
''' there are so many views, this just makes sure it LOADS '''
view = views.Status.as_view()
status = models.Status.objects.create(
content='hi', user=self.local_user)
request = self.factory.get('')
request.user = self.local_user
with patch('bookwyrm.views.status.is_api_request') as is_api:
is_api.return_value = False
result = view(request, 'mouse', status.id)
self.assertIsInstance(result, TemplateResponse)
self.assertEqual(result.template_name, 'status.html')
self.assertEqual(result.status_code, 200)
with patch('bookwyrm.views.status.is_api_request') as is_api:
is_api.return_value = True
result = view(request, 'mouse', status.id)
self.assertIsInstance(result, ActivitypubResponse)
self.assertEqual(result.status_code, 200)
def test_replies_page(self):
''' there are so many views, this just makes sure it LOADS '''
view = views.Replies.as_view()
status = models.Status.objects.create(
content='hi', user=self.local_user)
request = self.factory.get('')
request.user = self.local_user
with patch('bookwyrm.views.status.is_api_request') as is_api:
is_api.return_value = False
result = view(request, 'mouse', status.id)
self.assertIsInstance(result, TemplateResponse)
self.assertEqual(result.template_name, 'status.html')
self.assertEqual(result.status_code, 200)
with patch('bookwyrm.views.status.is_api_request') as is_api:
is_api.return_value = True
result = view(request, 'mouse', status.id)
self.assertIsInstance(result, ActivitypubResponse)
self.assertEqual(result.status_code, 200)
def test_handle_status(self):
''' create a status '''
view = views.CreateStatus.as_view()

View file

@ -34,7 +34,7 @@ class UserViews(TestCase):
is_api.return_value = False
result = view(request, 'mouse')
self.assertIsInstance(result, TemplateResponse)
self.assertEqual(result.template_name, 'user.html')
self.assertEqual(result.template_name, 'user/user.html')
self.assertEqual(result.status_code, 200)
with patch('bookwyrm.views.user.is_api_request') as is_api:
@ -65,7 +65,7 @@ class UserViews(TestCase):
is_api.return_value = False
result = view(request, 'mouse')
self.assertIsInstance(result, TemplateResponse)
self.assertEqual(result.template_name, 'followers.html')
self.assertEqual(result.template_name, 'user/followers.html')
self.assertEqual(result.status_code, 200)
with patch('bookwyrm.views.user.is_api_request') as is_api:
@ -96,7 +96,7 @@ class UserViews(TestCase):
is_api.return_value = False
result = view(request, 'mouse')
self.assertIsInstance(result, TemplateResponse)
self.assertEqual(result.template_name, 'following.html')
self.assertEqual(result.template_name, 'user/following.html')
self.assertEqual(result.status_code, 200)
with patch('bookwyrm.views.user.is_api_request') as is_api:
@ -125,7 +125,7 @@ class UserViews(TestCase):
request.user = self.local_user
result = view(request)
self.assertIsInstance(result, TemplateResponse)
self.assertEqual(result.template_name, 'edit_user.html')
self.assertEqual(result.template_name, 'preferences/edit_user.html')
self.assertEqual(result.status_code, 200)

View file

@ -5,7 +5,6 @@ from django.urls import path, re_path
from bookwyrm import incoming, settings, views, wellknown
from bookwyrm.views.rss_feed import RssFeed
from bookwyrm.utils import regex
user_path = r'^user/(?P<username>%s)' % regex.username
@ -49,7 +48,6 @@ urlpatterns = [
re_path(r'^password-reset/?$', views.PasswordResetRequest.as_view()),
re_path(r'^password-reset/(?P<code>[A-Za-z0-9]+)/?$',
views.PasswordReset.as_view()),
re_path(r'^change-password/?$', views.ChangePassword.as_view()),
# invites
re_path(r'^invite/?$', views.ManageInvites.as_view()),
@ -58,9 +56,11 @@ urlpatterns = [
# landing pages
re_path(r'^about/?$', views.About.as_view()),
path('', views.Home.as_view()),
re_path(r'^(?P<tab>home|local|federated)/?$', views.Feed.as_view()),
re_path(r'^discover/?$', views.Discover.as_view()),
re_path(r'^notifications/?$', views.Notifications.as_view()),
# feeds
re_path(r'^(?P<tab>home|local|federated)/?$', views.Feed.as_view()),
re_path(r'^direct-messages/?$', views.DirectMessage.as_view()),
# search
@ -76,9 +76,15 @@ urlpatterns = [
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/following(.json)?/?$' % user_path, views.Following.as_view()),
re_path(r'^edit-profile/?$', views.EditUser.as_view()),
re_path(r'%s/rss' % user_path, views.rss_feed.RssFeed()),
# preferences
re_path(r'^preferences/profile/?$', views.EditUser.as_view()),
re_path(r'^preferences/password/?$', views.ChangePassword.as_view()),
re_path(r'^preferences/block/?$', views.Block.as_view()),
re_path(r'^block/(?P<user_id>\d+)/?$', views.Block.as_view()),
re_path(r'^unblock/(?P<user_id>\d+)/?$', views.unblock),
# reading goals
re_path(r'%s/goal/(?P<year>\d{4})/?$' % user_path, views.Goal.as_view()),
@ -140,7 +146,4 @@ urlpatterns = [
re_path(r'^accept-follow-request/?$', views.accept_follow_request),
re_path(r'^delete-follow-request/?$', views.delete_follow_request),
re_path(r'^block/?$', views.Block.as_view()),
re_path(r'^block/(?P<user_id>\d+)/?$', views.Block.as_view()),
re_path(r'^unblock/(?P<user_id>\d+)/?$', views.unblock),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View file

@ -4,25 +4,26 @@ from .author import Author, EditAuthor
from .block import Block, unblock
from .books import Book, EditBook, Editions
from .books import upload_cover, add_description, switch_edition, resolve_book
from .direct_message import DirectMessage
from .error import not_found_page, server_error_page
from .feed import DirectMessage, Feed, Replies, Status
from .follow import follow, unfollow
from .follow import accept_follow_request, delete_follow_request, handle_accept
from .goal import Goal
from .import_data import Import, ImportStatus
from .interaction import Favorite, Unfavorite, Boost, Unboost
from .invite import ManageInvites, Invite
from .landing import About, Home, Feed, Discover
from .landing import About, Home, Discover
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 .rss_feed import RssFeed
from .password import PasswordResetRequest, PasswordReset, ChangePassword
from .tag import Tag, AddTag, RemoveTag
from .search import Search
from .shelf import Shelf
from .shelf import user_shelves_page, create_shelf, delete_shelf
from .shelf import shelve, unshelve
from .status import Status, Replies, CreateStatus, DeleteStatus
from .status import CreateStatus, DeleteStatus
from .updates import Updates
from .user import User, EditUser, Followers, Following

View file

@ -17,7 +17,7 @@ class Block(View):
def get(self, request):
''' list of blocked users? '''
return TemplateResponse(
request, 'blocks.html', {'title': 'Blocked Users'})
request, 'preferences/blocks.html', {'title': 'Blocked Users'})
def post(self, request, user_id):
''' block a user '''
@ -31,7 +31,7 @@ class Block(View):
privacy='direct',
direct_recipients=[to_block]
)
return redirect('/block')
return redirect('/preferences/block')
@require_POST
@ -55,4 +55,4 @@ def unblock(request, user_id):
direct_recipients=[to_unblock]
)
block.delete()
return redirect('/block')
return redirect('/preferences/block')

View file

@ -1,26 +0,0 @@
''' non-interactive pages '''
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.template.response import TemplateResponse
from django.utils.decorators import method_decorator
from django.views import View
from bookwyrm.settings import PAGE_LENGTH
from .helpers import get_activity_feed
# pylint: disable= no-self-use
@method_decorator(login_required, name='dispatch')
class DirectMessage(View):
''' dm view '''
def get(self, request, page=1):
''' like a feed but for dms only '''
activities = get_activity_feed(request.user, 'direct')
paginated = Paginator(activities, PAGE_LENGTH)
activity_page = paginated.page(page)
data = {
'title': 'Direct Messages',
'user': request.user,
'activities': activity_page,
}
return TemplateResponse(request, 'direct_messages.html', data)

156
bookwyrm/views/feed.py Normal file
View file

@ -0,0 +1,156 @@
''' non-interactive pages '''
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.http import HttpResponseNotFound
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.activitypub import ActivitypubResponse
from bookwyrm.settings import PAGE_LENGTH
from .helpers import get_activity_feed
from .helpers import get_user_from_username
from .helpers import is_api_request, is_bookworm_request, object_visible_to_user
# pylint: disable= no-self-use
@method_decorator(login_required, name='dispatch')
class Feed(View):
''' activity stream '''
def get(self, request, tab):
''' user's homepage with activity feed '''
try:
page = int(request.GET.get('page', 1))
except ValueError:
page = 1
if tab == 'home':
activities = get_activity_feed(
request.user, ['public', 'unlisted', 'followers'],
following_only=True)
elif tab == 'local':
activities = get_activity_feed(
request.user, ['public', 'followers'], local_only=True)
else:
activities = get_activity_feed(
request.user, ['public', 'followers'])
paginated = Paginator(activities, PAGE_LENGTH)
data = {**feed_page_data(request.user), **{
'title': 'Updates Feed',
'user': request.user,
'activities': paginated.page(page),
'tab': tab,
'goal_form': forms.GoalForm(),
}}
return TemplateResponse(request, 'feed/feed.html', data)
@method_decorator(login_required, name='dispatch')
class DirectMessage(View):
''' dm view '''
def get(self, request):
''' like a feed but for dms only '''
try:
page = int(request.GET.get('page', 1))
except ValueError:
page = 1
activities = get_activity_feed(request.user, 'direct')
paginated = Paginator(activities, PAGE_LENGTH)
activity_page = paginated.page(page)
data = {**feed_page_data(request.user), **{
'title': 'Direct Messages',
'user': request.user,
'activities': activity_page,
}}
return TemplateResponse(request, 'feed/direct_messages.html', data)
class Status(View):
''' get posting '''
def get(self, request, username, status_id):
''' display a particular status (and replies, etc) '''
try:
user = get_user_from_username(username)
status = models.Status.objects.select_subclasses().get(
id=status_id, deleted=False)
except ValueError:
return HttpResponseNotFound()
# the url should have the poster's username in it
if user != status.user:
return HttpResponseNotFound()
# make sure the user is authorized to see the status
if not object_visible_to_user(request.user, status):
return HttpResponseNotFound()
if is_api_request(request):
return ActivitypubResponse(
status.to_activity(pure=not is_bookworm_request(request)))
data = {**feed_page_data(request.user), **{
'title': 'Status by %s' % user.username,
'status': status,
}}
return TemplateResponse(request, 'feed/status.html', data)
class Replies(View):
''' replies page (a json view of status) '''
def get(self, request, username, status_id):
''' ordered collection of replies to a status '''
# the html view is the same as Status
if not is_api_request(request):
status_view = Status.as_view()
return status_view(request, username, status_id)
# the json view is different than Status
status = models.Status.objects.get(id=status_id)
if status.user.localname != username:
return HttpResponseNotFound()
return ActivitypubResponse(status.to_replies(**request.GET))
def feed_page_data(user):
''' info we need for every feed page '''
if not user.is_authenticated:
return {}
goal = models.AnnualGoal.objects.filter(
user=user, year=timezone.now().year
).first()
return {
'suggested_books': get_suggested_books(user),
'goal': goal,
'goal_form': forms.GoalForm(),
}
def get_suggested_books(user, max_books=5):
''' helper to get a user's recent books '''
book_count = 0
preset_shelves = [
('reading', max_books), ('read', 2), ('to-read', max_books)
]
suggested_books = []
for (preset, shelf_max) in preset_shelves:
limit = shelf_max if shelf_max < (max_books - book_count) \
else max_books - book_count
shelf = user.shelf_set.get(identifier=preset)
shelf_books = shelf.shelfbook_set.order_by(
'-updated_date'
).all()[:limit]
if not shelf_books:
continue
shelf_preview = {
'name': shelf.name,
'books': [s.book for s in shelf_books]
}
suggested_books.append(shelf_preview)
book_count += len(shelf_preview['books'])
return suggested_books

View file

@ -1,14 +1,10 @@
''' non-interactive pages '''
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.db.models import Avg, Max
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.settings import PAGE_LENGTH
from .feed import Feed
from .helpers import get_activity_feed
@ -61,68 +57,3 @@ class Discover(View):
'ratings': ratings
}
return TemplateResponse(request, 'discover.html', data)
@method_decorator(login_required, name='dispatch')
class Feed(View):
''' activity stream '''
def get(self, request, tab):
''' user's homepage with activity feed '''
try:
page = int(request.GET.get('page', 1))
except ValueError:
page = 1
suggested_books = get_suggested_books(request.user)
if tab == 'home':
activities = get_activity_feed(
request.user, ['public', 'unlisted', 'followers'],
following_only=True)
elif tab == 'local':
activities = get_activity_feed(
request.user, ['public', 'followers'], local_only=True)
else:
activities = get_activity_feed(
request.user, ['public', 'followers'])
paginated = Paginator(activities, PAGE_LENGTH)
goal = models.AnnualGoal.objects.filter(
user=request.user, year=timezone.now().year
).first()
data = {
'title': 'Updates Feed',
'user': request.user,
'suggested_books': suggested_books,
'activities': paginated.page(page),
'tab': tab,
'goal': goal,
'goal_form': forms.GoalForm(),
}
return TemplateResponse(request, 'feed.html', data)
def get_suggested_books(user, max_books=5):
''' helper to get a user's recent books '''
book_count = 0
preset_shelves = [
('reading', max_books), ('read', 2), ('to-read', max_books)
]
suggested_books = []
for (preset, shelf_max) in preset_shelves:
limit = shelf_max if shelf_max < (max_books - book_count) \
else max_books - book_count
shelf = user.shelf_set.get(identifier=preset)
shelf_books = shelf.shelfbook_set.order_by(
'-updated_date'
).all()[:limit]
if not shelf_books:
continue
shelf_preview = {
'name': shelf.name,
'books': [s.book for s in shelf_books]
}
suggested_books.append(shelf_preview)
book_count += len(shelf_preview['books'])
return suggested_books

View file

@ -94,7 +94,8 @@ class ChangePassword(View):
'title': 'Change Password',
'user': request.user,
}
return TemplateResponse(request, 'change_password.html', data)
return TemplateResponse(
request, 'preferences/change_password.html', data)
def post(self, request):
''' allow a user to change their password '''
@ -102,9 +103,9 @@ class ChangePassword(View):
confirm_password = request.POST.get('confirm-password')
if new_password != confirm_password:
return redirect('/edit-profile')
return redirect('preferences/password')
request.user.set_password(new_password)
request.user.save()
login(request, request.user)
return redirect('/user/%s' % request.user.localname)
return redirect(request.user.local_path)

View file

@ -1,29 +1,35 @@
''' '''
''' serialize user's posts in rss feed '''
from django.contrib.syndication.views import Feed
from django.urls import reverse
from bookwyrm.models.user import User
from .helpers import get_activity_feed, get_user_from_username
# pylint: disable=no-self-use, unused-argument
class RssFeed(Feed):
description_template = "snippets/rss_content.html"
title_template = "snippets/rss_title.html"
''' serialize user's posts in rss feed '''
description_template = 'snippets/rss_content.html'
title_template = 'snippets/rss_title.html'
def get_object(self, request, username):
''' the user who's posts get serialized '''
return get_user_from_username(username)
def link(self, obj):
''' link to the user's profile '''
return obj.local_path
def title(self, obj):
return f"Status updates from {obj.display_name}"
''' title of the rss feed entry '''
return f'Status updates from {obj.display_name}'
def items(self, obj):
return get_activity_feed(obj, ['public', 'unlisted'], queryset=obj.status_set)
''' the user's activity feed '''
return get_activity_feed(
obj, ['public', 'unlisted'], queryset=obj.status_set)
def item_link(self, item):
''' link to the status '''
return item.local_path

View file

@ -1,55 +1,22 @@
''' what are we here for if not for posting '''
import re
from django.contrib.auth.decorators import login_required
from django.http import HttpResponseBadRequest, HttpResponseNotFound
from django.http import HttpResponseBadRequest
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 markdown import markdown
from bookwyrm import forms, models
from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.broadcast import broadcast
from bookwyrm.sanitize_html import InputHtmlParser
from bookwyrm.settings import DOMAIN
from bookwyrm.status import create_notification, delete_status
from bookwyrm.utils import regex
from .helpers import get_user_from_username, handle_remote_webfinger
from .helpers import is_api_request, is_bookworm_request, object_visible_to_user
from .helpers import handle_remote_webfinger
# pylint: disable= no-self-use
class Status(View):
''' get posting '''
def get(self, request, username, status_id):
''' display a particular status (and replies, etc) '''
try:
user = get_user_from_username(username)
status = models.Status.objects.select_subclasses().get(
id=status_id, deleted=False)
except ValueError:
return HttpResponseNotFound()
# the url should have the poster's username in it
if user != status.user:
return HttpResponseNotFound()
# make sure the user is authorized to see the status
if not object_visible_to_user(request.user, status):
return HttpResponseNotFound()
if is_api_request(request):
return ActivitypubResponse(
status.to_activity(pure=not is_bookworm_request(request)))
data = {
'title': 'Status by %s' % user.username,
'status': status,
}
return TemplateResponse(request, 'status.html', data)
@method_decorator(login_required, name='dispatch')
class CreateStatus(View):
''' the view for *posting* '''
@ -144,23 +111,6 @@ class DeleteStatus(View):
broadcast(request.user, status.to_delete_activity(request.user))
return redirect(request.headers.get('Referer', '/'))
class Replies(View):
''' replies page (a json view of status) '''
def get(self, request, username, status_id):
''' ordered collection of replies to a status '''
# the html view is the same as Status
if not is_api_request(request):
status_view = Status.as_view()
return status_view(request, username, status_id)
# the json view is different than Status
status = models.Status.objects.get(id=status_id)
if status.user.localname != username:
return HttpResponseNotFound()
return ActivitypubResponse(status.to_replies(**request.GET))
def find_mentions(content):
''' detect @mentions in raw status content '''
for match in re.finditer(regex.strict_username, content):

View file

@ -90,7 +90,7 @@ class User(View):
'goal': goal,
}
return TemplateResponse(request, 'user.html', data)
return TemplateResponse(request, 'user/user.html', data)
class Followers(View):
''' list of followers view '''
@ -115,7 +115,7 @@ class Followers(View):
'is_self': request.user.id == user.id,
'followers': user.followers.all(),
}
return TemplateResponse(request, 'followers.html', data)
return TemplateResponse(request, 'user/followers.html', data)
class Following(View):
''' list of following view '''
@ -140,7 +140,7 @@ class Following(View):
'is_self': request.user.id == user.id,
'following': user.following.all(),
}
return TemplateResponse(request, 'following.html', data)
return TemplateResponse(request, 'user/following.html', data)
@method_decorator(login_required, name='dispatch')
@ -153,7 +153,7 @@ class EditUser(View):
'form': forms.EditUserForm(instance=request.user),
'user': request.user,
}
return TemplateResponse(request, 'edit_user.html', data)
return TemplateResponse(request, 'preferences/edit_user.html', data)
def post(self, request):
''' les get fancy with images '''
@ -161,7 +161,7 @@ class EditUser(View):
request.POST, request.FILES, instance=request.user)
if not form.is_valid():
data = {'form': form, 'user': request.user}
return TemplateResponse(request, 'edit_user.html', data)
return TemplateResponse(request, 'preferences/edit_user.html', data)
user = form.save(commit=False)