Merge pull request #567 from mouse-reeve/organize-templates
Organize templates
This commit is contained in:
commit
94974d9f73
|
@ -35,8 +35,17 @@ window.onload = function() {
|
||||||
// polling
|
// polling
|
||||||
document.querySelectorAll('[data-poll]')
|
document.querySelectorAll('[data-poll]')
|
||||||
.forEach(el => polling(el));
|
.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) {
|
function polling(el) {
|
||||||
let delay = 10000 + (Math.random() * 1000);
|
let delay = 10000 + (Math.random() * 1000);
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends 'layout.html' %}
|
{% extends 'feed/feed_layout.html' %}
|
||||||
{% block content %}
|
{% block panel %}
|
||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<h1 class="title">Direct Messages</h1>
|
<h1 class="title">Direct Messages</h1>
|
|
@ -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 %}
|
|
@ -3,6 +3,7 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
|
{% if user.is_authenticated %}
|
||||||
<div class="column is-one-third">
|
<div class="column is-one-third">
|
||||||
<h2 class="title is-5">Your books</h2>
|
<h2 class="title is-5">Your books</h2>
|
||||||
{% if not suggested_books %}
|
{% if not suggested_books %}
|
||||||
|
@ -69,43 +70,15 @@
|
||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="column is-two-thirds" id="feed">
|
<div class="column is-two-thirds" id="feed">
|
||||||
<h1 class="title">{{ tab | title }} Timeline</h1>
|
{% block panel %}{% endblock %}
|
||||||
<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 %}
|
|
||||||
|
|
||||||
|
{% if activities %}
|
||||||
{% include 'snippets/pagination.html' with page=activities path='/'|add:tab anchor="#feed" %}
|
{% include 'snippets/pagination.html' with page=activities path='/'|add:tab anchor="#feed" %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -81,7 +81,7 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/edit-profile" class="navbar-item">
|
<a href="/preferences/profile" class="navbar-item">
|
||||||
Settings
|
Settings
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'preferences_layout.html' %}
|
{% extends 'preferences/preferences_layout.html' %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
Blocked Users
|
Blocked Users
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'preferences_layout.html' %}
|
{% extends 'preferences/preferences_layout.html' %}
|
||||||
{% block header %}
|
{% block header %}
|
||||||
Change Password
|
Change Password
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'preferences_layout.html' %}
|
{% extends 'preferences/preferences_layout.html' %}
|
||||||
{% block header %}
|
{% block header %}
|
||||||
Edit Profile
|
Edit Profile
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -10,16 +10,16 @@
|
||||||
<h2 class="menu-label">Account</h2>
|
<h2 class="menu-label">Account</h2>
|
||||||
<ul class="menu-list">
|
<ul class="menu-list">
|
||||||
<li>
|
<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>
|
||||||
<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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h2 class="menu-label">Relationships</h2>
|
<h2 class="menu-label">Relationships</h2>
|
||||||
<ul class="menu-list">
|
<ul class="menu-list">
|
||||||
<li>
|
<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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'snippets/components/modal.html' %}
|
{% extends 'components/modal.html' %}
|
||||||
{% block modal-title %}Delete these read dates?{% endblock %}
|
{% block modal-title %}Delete these read dates?{% endblock %}
|
||||||
{% block modal-body %}
|
{% block modal-body %}
|
||||||
{% if readthrough.progress_updates|length > 0 %}
|
{% if readthrough.progress_updates|length > 0 %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'snippets/components/modal.html' %}
|
{% extends 'components/modal.html' %}
|
||||||
|
|
||||||
{% block modal-title %}
|
{% block modal-title %}
|
||||||
Finish "<em>{{ book.title }}</em>"
|
Finish "<em>{{ book.title }}</em>"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'snippets/components/card.html' %}
|
{% extends 'components/card.html' %}
|
||||||
|
|
||||||
{% block card-header %}
|
{% block card-header %}
|
||||||
<h3 class="card-header-title has-background-primary has-text-white">
|
<h3 class="card-header-title has-background-primary has-text-white">
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'snippets/components/dropdown.html' %}
|
{% extends 'components/dropdown.html' %}
|
||||||
{% block dropdown-trigger %}
|
{% block dropdown-trigger %}
|
||||||
<span>Change shelf</span>
|
<span>Change shelf</span>
|
||||||
<span class="icon icon-arrow-down" aria-hidden="true"></span>
|
<span class="icon icon-arrow-down" aria-hidden="true"></span>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'snippets/components/dropdown.html' %}
|
{% extends 'components/dropdown.html' %}
|
||||||
{% block dropdown-trigger %}
|
{% block dropdown-trigger %}
|
||||||
<span class="icon icon-arrow-down">
|
<span class="icon icon-arrow-down">
|
||||||
<span class="is-sr-only">More shelves</span>
|
<span class="is-sr-only">More shelves</span>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'snippets/components/modal.html' %}
|
{% extends 'components/modal.html' %}
|
||||||
|
|
||||||
{% block modal-title %}
|
{% block modal-title %}
|
||||||
Start "<em>{{ book.title }}</em>"
|
Start "<em>{{ book.title }}</em>"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'snippets/components/card.html' %}
|
{% extends 'components/card.html' %}
|
||||||
|
|
||||||
{% load bookwyrm_tags %}
|
{% load bookwyrm_tags %}
|
||||||
{% load humanize %}
|
{% load humanize %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'snippets/components/dropdown.html' %}
|
{% extends 'components/dropdown.html' %}
|
||||||
{% load bookwyrm_tags %}
|
{% load bookwyrm_tags %}
|
||||||
|
|
||||||
{% block dropdown-trigger %}
|
{% block dropdown-trigger %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'snippets/components/dropdown.html' %}
|
{% extends 'components/dropdown.html' %}
|
||||||
{% load bookwyrm_tags %}
|
{% load bookwyrm_tags %}
|
||||||
|
|
||||||
{% block dropdown-trigger %}
|
{% block dropdown-trigger %}
|
||||||
|
|
|
@ -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 %}
|
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
{% extends 'layout.html' %}
|
{% extends 'user/user_layout.html' %}
|
||||||
{% load bookwyrm_tags %}
|
{% 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">
|
<div class="block">
|
||||||
<h2 class="title">Followers</h2>
|
<h2 class="title">Followers</h2>
|
||||||
{% for followers in followers %}
|
{% for followers in followers %}
|
||||||
|
@ -34,5 +33,4 @@
|
||||||
<div>{{ user|username }} has no followers</div>
|
<div>{{ user|username }} has no followers</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,18 +1,17 @@
|
||||||
{% extends 'layout.html' %}
|
{% extends 'user/user_layout.html' %}
|
||||||
{% load bookwyrm_tags %}
|
{% 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">
|
<div class="block">
|
||||||
<h2 class="title">Following</h2>
|
<h2 class="title">Following</h2>
|
||||||
{% for follower in user.following.all %}
|
{% for follower in user.following.all %}
|
||||||
|
@ -34,5 +33,4 @@
|
||||||
<div>{{ user|username }} isn't following any users</div>
|
<div>{{ user|username }} isn't following any users</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,13 +1,13 @@
|
||||||
{% extends 'layout.html' %}
|
{% extends 'user/user_layout.html' %}
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<h1 class="title">User profile</h1>
|
<h1 class="title">User profile</h1>
|
||||||
</div>
|
</div>
|
||||||
{% if is_self %}
|
{% if is_self %}
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
<a href="/edit-profile">
|
<a href="/preferences/profile">
|
||||||
<span class="icon icon-pencil" title="Edit profile">
|
<span class="icon icon-pencil" title="Edit profile">
|
||||||
<span class="is-sr-only">Edit profile</span>
|
<span class="is-sr-only">Edit profile</span>
|
||||||
</span>
|
</span>
|
||||||
|
@ -15,8 +15,9 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% include 'snippets/user_header.html' with user=user %}
|
{% block panel %}
|
||||||
{% if user.bookwyrm_user %}
|
{% if user.bookwyrm_user %}
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<h2 class="title">Shelves</h2>
|
<h2 class="title">Shelves</h2>
|
|
@ -1,5 +1,13 @@
|
||||||
|
{% extends 'layout.html' %}
|
||||||
{% load humanize %}
|
{% load humanize %}
|
||||||
{% load bookwyrm_tags %}
|
{% load bookwyrm_tags %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<header class="block">
|
||||||
|
{% block header %}{% endblock %}
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{# user bio #}
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-narrow">
|
<div class="column is-narrow">
|
||||||
|
@ -60,3 +68,6 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% block panel %}{% endblock %}
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -32,7 +32,7 @@ class BlockViews(TestCase):
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
result = view(request)
|
result = view(request)
|
||||||
self.assertIsInstance(result, TemplateResponse)
|
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)
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
def test_block_post(self):
|
def test_block_post(self):
|
||||||
|
|
|
@ -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)
|
|
|
@ -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)
|
|
@ -18,10 +18,6 @@ class LandingViews(TestCase):
|
||||||
local=True, localname='mouse')
|
local=True, localname='mouse')
|
||||||
self.anonymous_user = AnonymousUser
|
self.anonymous_user = AnonymousUser
|
||||||
self.anonymous_user.is_authenticated = False
|
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):
|
def test_home_page(self):
|
||||||
|
@ -31,7 +27,7 @@ class LandingViews(TestCase):
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
result = view(request)
|
result = view(request)
|
||||||
self.assertEqual(result.status_code, 200)
|
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
|
request.user = self.anonymous_user
|
||||||
result = view(request)
|
result = view(request)
|
||||||
|
@ -51,17 +47,6 @@ class LandingViews(TestCase):
|
||||||
self.assertEqual(result.status_code, 200)
|
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):
|
def test_discover(self):
|
||||||
''' there are so many views, this just makes sure it LOADS '''
|
''' there are so many views, this just makes sure it LOADS '''
|
||||||
view = views.Discover.as_view()
|
view = views.Discover.as_view()
|
||||||
|
@ -70,15 +55,3 @@ class LandingViews(TestCase):
|
||||||
self.assertIsInstance(result, TemplateResponse)
|
self.assertIsInstance(result, TemplateResponse)
|
||||||
self.assertEqual(result.template_name, 'discover.html')
|
self.assertEqual(result.template_name, 'discover.html')
|
||||||
self.assertEqual(result.status_code, 200)
|
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)
|
|
||||||
|
|
|
@ -106,7 +106,7 @@ class PasswordViews(TestCase):
|
||||||
|
|
||||||
result = view(request)
|
result = view(request)
|
||||||
self.assertIsInstance(result, TemplateResponse)
|
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)
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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):
|
def test_handle_status(self):
|
||||||
''' create a status '''
|
''' create a status '''
|
||||||
view = views.CreateStatus.as_view()
|
view = views.CreateStatus.as_view()
|
||||||
|
|
|
@ -34,7 +34,7 @@ class UserViews(TestCase):
|
||||||
is_api.return_value = False
|
is_api.return_value = False
|
||||||
result = view(request, 'mouse')
|
result = view(request, 'mouse')
|
||||||
self.assertIsInstance(result, TemplateResponse)
|
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)
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
with patch('bookwyrm.views.user.is_api_request') as is_api:
|
with patch('bookwyrm.views.user.is_api_request') as is_api:
|
||||||
|
@ -65,7 +65,7 @@ class UserViews(TestCase):
|
||||||
is_api.return_value = False
|
is_api.return_value = False
|
||||||
result = view(request, 'mouse')
|
result = view(request, 'mouse')
|
||||||
self.assertIsInstance(result, TemplateResponse)
|
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)
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
with patch('bookwyrm.views.user.is_api_request') as is_api:
|
with patch('bookwyrm.views.user.is_api_request') as is_api:
|
||||||
|
@ -96,7 +96,7 @@ class UserViews(TestCase):
|
||||||
is_api.return_value = False
|
is_api.return_value = False
|
||||||
result = view(request, 'mouse')
|
result = view(request, 'mouse')
|
||||||
self.assertIsInstance(result, TemplateResponse)
|
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)
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
with patch('bookwyrm.views.user.is_api_request') as is_api:
|
with patch('bookwyrm.views.user.is_api_request') as is_api:
|
||||||
|
@ -125,7 +125,7 @@ class UserViews(TestCase):
|
||||||
request.user = self.local_user
|
request.user = self.local_user
|
||||||
result = view(request)
|
result = view(request)
|
||||||
self.assertIsInstance(result, TemplateResponse)
|
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)
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ from django.urls import path, re_path
|
||||||
|
|
||||||
|
|
||||||
from bookwyrm import incoming, settings, views, wellknown
|
from bookwyrm import incoming, settings, views, wellknown
|
||||||
from bookwyrm.views.rss_feed import RssFeed
|
|
||||||
from bookwyrm.utils import regex
|
from bookwyrm.utils import regex
|
||||||
|
|
||||||
user_path = r'^user/(?P<username>%s)' % regex.username
|
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/?$', views.PasswordResetRequest.as_view()),
|
||||||
re_path(r'^password-reset/(?P<code>[A-Za-z0-9]+)/?$',
|
re_path(r'^password-reset/(?P<code>[A-Za-z0-9]+)/?$',
|
||||||
views.PasswordReset.as_view()),
|
views.PasswordReset.as_view()),
|
||||||
re_path(r'^change-password/?$', views.ChangePassword.as_view()),
|
|
||||||
|
|
||||||
# invites
|
# invites
|
||||||
re_path(r'^invite/?$', views.ManageInvites.as_view()),
|
re_path(r'^invite/?$', views.ManageInvites.as_view()),
|
||||||
|
@ -58,9 +56,11 @@ urlpatterns = [
|
||||||
# landing pages
|
# landing pages
|
||||||
re_path(r'^about/?$', views.About.as_view()),
|
re_path(r'^about/?$', views.About.as_view()),
|
||||||
path('', views.Home.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'^discover/?$', views.Discover.as_view()),
|
||||||
re_path(r'^notifications/?$', views.Notifications.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()),
|
re_path(r'^direct-messages/?$', views.DirectMessage.as_view()),
|
||||||
|
|
||||||
# search
|
# search
|
||||||
|
@ -76,9 +76,15 @@ urlpatterns = [
|
||||||
re_path(r'%s/shelves/?$' % user_path, views.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'%s/rss' % user_path, views.rss_feed.RssFeed()),
|
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
|
# reading goals
|
||||||
re_path(r'%s/goal/(?P<year>\d{4})/?$' % user_path, views.Goal.as_view()),
|
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'^accept-follow-request/?$', views.accept_follow_request),
|
||||||
re_path(r'^delete-follow-request/?$', views.delete_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)
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
|
@ -4,25 +4,26 @@ from .author import Author, EditAuthor
|
||||||
from .block import Block, unblock
|
from .block import Block, unblock
|
||||||
from .books import Book, EditBook, Editions
|
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 .direct_message import DirectMessage
|
|
||||||
from .error import not_found_page, server_error_page
|
from .error import not_found_page, server_error_page
|
||||||
|
from .feed import DirectMessage, Feed, Replies, Status
|
||||||
from .follow import follow, unfollow
|
from .follow import follow, unfollow
|
||||||
from .follow import accept_follow_request, delete_follow_request, handle_accept
|
from .follow import accept_follow_request, delete_follow_request, handle_accept
|
||||||
from .goal import Goal
|
from .goal import Goal
|
||||||
from .import_data import Import, ImportStatus
|
from .import_data import Import, ImportStatus
|
||||||
from .interaction import Favorite, Unfavorite, Boost, Unboost
|
from .interaction import Favorite, Unfavorite, Boost, Unboost
|
||||||
from .invite import ManageInvites, Invite
|
from .invite import ManageInvites, Invite
|
||||||
from .landing import About, Home, Feed, Discover
|
from .landing import About, Home, Discover
|
||||||
from .notifications import Notifications
|
from .notifications import Notifications
|
||||||
from .outbox import Outbox
|
from .outbox import Outbox
|
||||||
from .reading import edit_readthrough, create_readthrough, delete_readthrough
|
from .reading import edit_readthrough, create_readthrough, delete_readthrough
|
||||||
from .reading import start_reading, finish_reading, delete_progressupdate
|
from .reading import start_reading, finish_reading, delete_progressupdate
|
||||||
|
from .rss_feed import RssFeed
|
||||||
from .password import PasswordResetRequest, PasswordReset, ChangePassword
|
from .password import PasswordResetRequest, PasswordReset, ChangePassword
|
||||||
from .tag import Tag, AddTag, RemoveTag
|
from .tag import Tag, AddTag, RemoveTag
|
||||||
from .search import Search
|
from .search import Search
|
||||||
from .shelf import Shelf
|
from .shelf import Shelf
|
||||||
from .shelf import user_shelves_page, create_shelf, delete_shelf
|
from .shelf import user_shelves_page, create_shelf, delete_shelf
|
||||||
from .shelf import shelve, unshelve
|
from .shelf import shelve, unshelve
|
||||||
from .status import Status, Replies, CreateStatus, DeleteStatus
|
from .status import CreateStatus, DeleteStatus
|
||||||
from .updates import Updates
|
from .updates import Updates
|
||||||
from .user import User, EditUser, Followers, Following
|
from .user import User, EditUser, Followers, Following
|
||||||
|
|
|
@ -17,7 +17,7 @@ class Block(View):
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
''' list of blocked users? '''
|
''' list of blocked users? '''
|
||||||
return TemplateResponse(
|
return TemplateResponse(
|
||||||
request, 'blocks.html', {'title': 'Blocked Users'})
|
request, 'preferences/blocks.html', {'title': 'Blocked Users'})
|
||||||
|
|
||||||
def post(self, request, user_id):
|
def post(self, request, user_id):
|
||||||
''' block a user '''
|
''' block a user '''
|
||||||
|
@ -31,7 +31,7 @@ class Block(View):
|
||||||
privacy='direct',
|
privacy='direct',
|
||||||
direct_recipients=[to_block]
|
direct_recipients=[to_block]
|
||||||
)
|
)
|
||||||
return redirect('/block')
|
return redirect('/preferences/block')
|
||||||
|
|
||||||
|
|
||||||
@require_POST
|
@require_POST
|
||||||
|
@ -55,4 +55,4 @@ def unblock(request, user_id):
|
||||||
direct_recipients=[to_unblock]
|
direct_recipients=[to_unblock]
|
||||||
)
|
)
|
||||||
block.delete()
|
block.delete()
|
||||||
return redirect('/block')
|
return redirect('/preferences/block')
|
||||||
|
|
|
@ -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)
|
|
|
@ -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
|
|
@ -1,14 +1,10 @@
|
||||||
''' non-interactive pages '''
|
''' 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.db.models import Avg, Max
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils import timezone
|
|
||||||
from django.utils.decorators import method_decorator
|
|
||||||
from django.views import View
|
from django.views import View
|
||||||
|
|
||||||
from bookwyrm import forms, models
|
from bookwyrm import forms, models
|
||||||
from bookwyrm.settings import PAGE_LENGTH
|
from .feed import Feed
|
||||||
from .helpers import get_activity_feed
|
from .helpers import get_activity_feed
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,68 +57,3 @@ class Discover(View):
|
||||||
'ratings': ratings
|
'ratings': ratings
|
||||||
}
|
}
|
||||||
return TemplateResponse(request, 'discover.html', data)
|
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
|
|
||||||
|
|
|
@ -94,7 +94,8 @@ class ChangePassword(View):
|
||||||
'title': 'Change Password',
|
'title': 'Change Password',
|
||||||
'user': request.user,
|
'user': request.user,
|
||||||
}
|
}
|
||||||
return TemplateResponse(request, 'change_password.html', data)
|
return TemplateResponse(
|
||||||
|
request, 'preferences/change_password.html', data)
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
''' allow a user to change their password '''
|
''' allow a user to change their password '''
|
||||||
|
@ -102,9 +103,9 @@ class ChangePassword(View):
|
||||||
confirm_password = request.POST.get('confirm-password')
|
confirm_password = request.POST.get('confirm-password')
|
||||||
|
|
||||||
if new_password != confirm_password:
|
if new_password != confirm_password:
|
||||||
return redirect('/edit-profile')
|
return redirect('preferences/password')
|
||||||
|
|
||||||
request.user.set_password(new_password)
|
request.user.set_password(new_password)
|
||||||
request.user.save()
|
request.user.save()
|
||||||
login(request, request.user)
|
login(request, request.user)
|
||||||
return redirect('/user/%s' % request.user.localname)
|
return redirect(request.user.local_path)
|
||||||
|
|
|
@ -1,29 +1,35 @@
|
||||||
''' '''
|
''' serialize user's posts in rss feed '''
|
||||||
|
|
||||||
from django.contrib.syndication.views import 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
|
from .helpers import get_activity_feed, get_user_from_username
|
||||||
|
|
||||||
|
# pylint: disable=no-self-use, unused-argument
|
||||||
class RssFeed(Feed):
|
class RssFeed(Feed):
|
||||||
|
''' serialize user's posts in rss feed '''
|
||||||
description_template = "snippets/rss_content.html"
|
description_template = 'snippets/rss_content.html'
|
||||||
title_template = "snippets/rss_title.html"
|
title_template = 'snippets/rss_title.html'
|
||||||
|
|
||||||
def get_object(self, request, username):
|
def get_object(self, request, username):
|
||||||
|
''' the user who's posts get serialized '''
|
||||||
return get_user_from_username(username)
|
return get_user_from_username(username)
|
||||||
|
|
||||||
|
|
||||||
def link(self, obj):
|
def link(self, obj):
|
||||||
|
''' link to the user's profile '''
|
||||||
return obj.local_path
|
return obj.local_path
|
||||||
|
|
||||||
|
|
||||||
def title(self, obj):
|
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):
|
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):
|
def item_link(self, item):
|
||||||
|
''' link to the status '''
|
||||||
return item.local_path
|
return item.local_path
|
||||||
|
|
||||||
|
|
|
@ -1,55 +1,22 @@
|
||||||
''' what are we here for if not for posting '''
|
''' what are we here for if not for posting '''
|
||||||
import re
|
import re
|
||||||
from django.contrib.auth.decorators import login_required
|
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.shortcuts import get_object_or_404, redirect
|
||||||
from django.template.response import TemplateResponse
|
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from markdown import markdown
|
from markdown import markdown
|
||||||
|
|
||||||
from bookwyrm import forms, models
|
from bookwyrm import forms, models
|
||||||
from bookwyrm.activitypub import ActivitypubResponse
|
|
||||||
from bookwyrm.broadcast import broadcast
|
from bookwyrm.broadcast import broadcast
|
||||||
from bookwyrm.sanitize_html import InputHtmlParser
|
from bookwyrm.sanitize_html import InputHtmlParser
|
||||||
from bookwyrm.settings import DOMAIN
|
from bookwyrm.settings import DOMAIN
|
||||||
from bookwyrm.status import create_notification, delete_status
|
from bookwyrm.status import create_notification, delete_status
|
||||||
from bookwyrm.utils import regex
|
from bookwyrm.utils import regex
|
||||||
from .helpers import get_user_from_username, handle_remote_webfinger
|
from .helpers import handle_remote_webfinger
|
||||||
from .helpers import is_api_request, is_bookworm_request, object_visible_to_user
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable= no-self-use
|
# 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')
|
@method_decorator(login_required, name='dispatch')
|
||||||
class CreateStatus(View):
|
class CreateStatus(View):
|
||||||
''' the view for *posting* '''
|
''' the view for *posting* '''
|
||||||
|
@ -144,23 +111,6 @@ class DeleteStatus(View):
|
||||||
broadcast(request.user, status.to_delete_activity(request.user))
|
broadcast(request.user, status.to_delete_activity(request.user))
|
||||||
return redirect(request.headers.get('Referer', '/'))
|
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):
|
def find_mentions(content):
|
||||||
''' detect @mentions in raw status content '''
|
''' detect @mentions in raw status content '''
|
||||||
for match in re.finditer(regex.strict_username, content):
|
for match in re.finditer(regex.strict_username, content):
|
||||||
|
|
|
@ -90,7 +90,7 @@ class User(View):
|
||||||
'goal': goal,
|
'goal': goal,
|
||||||
}
|
}
|
||||||
|
|
||||||
return TemplateResponse(request, 'user.html', data)
|
return TemplateResponse(request, 'user/user.html', data)
|
||||||
|
|
||||||
class Followers(View):
|
class Followers(View):
|
||||||
''' list of followers view '''
|
''' list of followers view '''
|
||||||
|
@ -115,7 +115,7 @@ class Followers(View):
|
||||||
'is_self': request.user.id == user.id,
|
'is_self': request.user.id == user.id,
|
||||||
'followers': user.followers.all(),
|
'followers': user.followers.all(),
|
||||||
}
|
}
|
||||||
return TemplateResponse(request, 'followers.html', data)
|
return TemplateResponse(request, 'user/followers.html', data)
|
||||||
|
|
||||||
class Following(View):
|
class Following(View):
|
||||||
''' list of following view '''
|
''' list of following view '''
|
||||||
|
@ -140,7 +140,7 @@ class Following(View):
|
||||||
'is_self': request.user.id == user.id,
|
'is_self': request.user.id == user.id,
|
||||||
'following': user.following.all(),
|
'following': user.following.all(),
|
||||||
}
|
}
|
||||||
return TemplateResponse(request, 'following.html', data)
|
return TemplateResponse(request, 'user/following.html', data)
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(login_required, name='dispatch')
|
@method_decorator(login_required, name='dispatch')
|
||||||
|
@ -153,7 +153,7 @@ class EditUser(View):
|
||||||
'form': forms.EditUserForm(instance=request.user),
|
'form': forms.EditUserForm(instance=request.user),
|
||||||
'user': request.user,
|
'user': request.user,
|
||||||
}
|
}
|
||||||
return TemplateResponse(request, 'edit_user.html', data)
|
return TemplateResponse(request, 'preferences/edit_user.html', data)
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
''' les get fancy with images '''
|
''' les get fancy with images '''
|
||||||
|
@ -161,7 +161,7 @@ class EditUser(View):
|
||||||
request.POST, request.FILES, instance=request.user)
|
request.POST, request.FILES, instance=request.user)
|
||||||
if not form.is_valid():
|
if not form.is_valid():
|
||||||
data = {'form': form, 'user': request.user}
|
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)
|
user = form.save(commit=False)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue