diff --git a/bookwyrm/static/js/shared.js b/bookwyrm/static/js/shared.js index a0c21bec..c2fa8b54 100644 --- a/bookwyrm/static/js/shared.js +++ b/bookwyrm/static/js/shared.js @@ -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() { diff --git a/bookwyrm/templates/snippets/components/card.html b/bookwyrm/templates/components/card.html similarity index 100% rename from bookwyrm/templates/snippets/components/card.html rename to bookwyrm/templates/components/card.html diff --git a/bookwyrm/templates/snippets/components/dropdown.html b/bookwyrm/templates/components/dropdown.html similarity index 100% rename from bookwyrm/templates/snippets/components/dropdown.html rename to bookwyrm/templates/components/dropdown.html diff --git a/bookwyrm/templates/snippets/components/modal.html b/bookwyrm/templates/components/modal.html similarity index 100% rename from bookwyrm/templates/snippets/components/modal.html rename to bookwyrm/templates/components/modal.html diff --git a/bookwyrm/templates/direct_messages.html b/bookwyrm/templates/feed/direct_messages.html similarity index 88% rename from bookwyrm/templates/direct_messages.html rename to bookwyrm/templates/feed/direct_messages.html index 666f5290..44a0cded 100644 --- a/bookwyrm/templates/direct_messages.html +++ b/bookwyrm/templates/feed/direct_messages.html @@ -1,5 +1,5 @@ -{% extends 'layout.html' %} -{% block content %} +{% extends 'feed/feed_layout.html' %} +{% block panel %}

Direct Messages

diff --git a/bookwyrm/templates/feed/feed.html b/bookwyrm/templates/feed/feed.html new file mode 100644 index 00000000..8d6152e2 --- /dev/null +++ b/bookwyrm/templates/feed/feed.html @@ -0,0 +1,39 @@ +{% extends 'feed/feed_layout.html' %} +{% load bookwyrm_tags %} +{% block panel %} + +

{{ tab | title }} Timeline

+
+ +
+ +{# announcements and system messages #} +{% if not goal and tab == 'home' %} +{% now 'Y' as year %} + +{% endif %} + +{# activity feed #} +{% if not activities %} +

There aren't any activities right now! Try following a user to get started

+{% endif %} +{% for activity in activities %} +
+{% include 'snippets/status.html' with status=activity %} +
+{% endfor %} + +{% endblock %} diff --git a/bookwyrm/templates/feed.html b/bookwyrm/templates/feed/feed_layout.html similarity index 73% rename from bookwyrm/templates/feed.html rename to bookwyrm/templates/feed/feed_layout.html index 1368660b..33123ca8 100644 --- a/bookwyrm/templates/feed.html +++ b/bookwyrm/templates/feed/feed_layout.html @@ -3,6 +3,7 @@ {% block content %}
+ {% if user.is_authenticated %}

Your books

{% if not suggested_books %} @@ -69,43 +70,15 @@ {% endif %}
+ {% endif %}
-

{{ tab | title }} Timeline

-
- -
- - {# announcements and system messages #} - {% if not goal and tab == 'home' %} - {% now 'Y' as year %} - - {% endif %} - - {# activity feed #} - {% if not activities %} -

There aren't any activities right now! Try following a user to get started

- {% endif %} - {% for activity in activities %} -
- {% include 'snippets/status.html' with status=activity %} -
- {% endfor %} + {% block panel %}{% endblock %} + {% if activities %} {% include 'snippets/pagination.html' with page=activities path='/'|add:tab anchor="#feed" %} + {% endif %}
{% endblock %} + diff --git a/bookwyrm/templates/feed/status.html b/bookwyrm/templates/feed/status.html new file mode 100644 index 00000000..eb848993 --- /dev/null +++ b/bookwyrm/templates/feed/status.html @@ -0,0 +1,13 @@ +{% extends 'feed/feed_layout.html' %} +{% block panel %} +
+ + + Back + +
+ +{% include 'feed/thread.html' with status=status depth=0 max_depth=6 is_root=True direction=0 %} + +{% endblock %} + diff --git a/bookwyrm/templates/snippets/thread.html b/bookwyrm/templates/feed/thread.html similarity index 100% rename from bookwyrm/templates/snippets/thread.html rename to bookwyrm/templates/feed/thread.html diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html index ddbafbb4..a696e287 100644 --- a/bookwyrm/templates/layout.html +++ b/bookwyrm/templates/layout.html @@ -81,7 +81,7 @@
  • - + Settings
  • diff --git a/bookwyrm/templates/blocks.html b/bookwyrm/templates/preferences/blocks.html similarity index 91% rename from bookwyrm/templates/blocks.html rename to bookwyrm/templates/preferences/blocks.html index 1df49816..5fd29d25 100644 --- a/bookwyrm/templates/blocks.html +++ b/bookwyrm/templates/preferences/blocks.html @@ -1,4 +1,4 @@ -{% extends 'preferences_layout.html' %} +{% extends 'preferences/preferences_layout.html' %} {% block header %} Blocked Users diff --git a/bookwyrm/templates/change_password.html b/bookwyrm/templates/preferences/change_password.html similarity index 93% rename from bookwyrm/templates/change_password.html rename to bookwyrm/templates/preferences/change_password.html index c373dfc8..b1b2a049 100644 --- a/bookwyrm/templates/change_password.html +++ b/bookwyrm/templates/preferences/change_password.html @@ -1,4 +1,4 @@ -{% extends 'preferences_layout.html' %} +{% extends 'preferences/preferences_layout.html' %} {% block header %} Change Password {% endblock %} diff --git a/bookwyrm/templates/edit_user.html b/bookwyrm/templates/preferences/edit_user.html similarity index 96% rename from bookwyrm/templates/edit_user.html rename to bookwyrm/templates/preferences/edit_user.html index ee9ddb22..8b384276 100644 --- a/bookwyrm/templates/edit_user.html +++ b/bookwyrm/templates/preferences/edit_user.html @@ -1,4 +1,4 @@ -{% extends 'preferences_layout.html' %} +{% extends 'preferences/preferences_layout.html' %} {% block header %} Edit Profile {% endblock %} diff --git a/bookwyrm/templates/preferences_layout.html b/bookwyrm/templates/preferences/preferences_layout.html similarity index 57% rename from bookwyrm/templates/preferences_layout.html rename to bookwyrm/templates/preferences/preferences_layout.html index de2fe0df..b0e7b31a 100644 --- a/bookwyrm/templates/preferences_layout.html +++ b/bookwyrm/templates/preferences/preferences_layout.html @@ -10,16 +10,16 @@ diff --git a/bookwyrm/templates/snippets/delete_readthrough_modal.html b/bookwyrm/templates/snippets/delete_readthrough_modal.html index c04a1d90..19c4b4f0 100644 --- a/bookwyrm/templates/snippets/delete_readthrough_modal.html +++ b/bookwyrm/templates/snippets/delete_readthrough_modal.html @@ -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 %} diff --git a/bookwyrm/templates/snippets/finish_reading_modal.html b/bookwyrm/templates/snippets/finish_reading_modal.html index 10af1162..c3407631 100644 --- a/bookwyrm/templates/snippets/finish_reading_modal.html +++ b/bookwyrm/templates/snippets/finish_reading_modal.html @@ -1,4 +1,4 @@ -{% extends 'snippets/components/modal.html' %} +{% extends 'components/modal.html' %} {% block modal-title %} Finish "{{ book.title }}" diff --git a/bookwyrm/templates/snippets/goal_card.html b/bookwyrm/templates/snippets/goal_card.html index b453d6e4..e26cd5a3 100644 --- a/bookwyrm/templates/snippets/goal_card.html +++ b/bookwyrm/templates/snippets/goal_card.html @@ -1,4 +1,4 @@ -{% extends 'snippets/components/card.html' %} +{% extends 'components/card.html' %} {% block card-header %}

    diff --git a/bookwyrm/templates/snippets/shelf_selector.html b/bookwyrm/templates/snippets/shelf_selector.html index f6d5607f..50d7e750 100644 --- a/bookwyrm/templates/snippets/shelf_selector.html +++ b/bookwyrm/templates/snippets/shelf_selector.html @@ -1,4 +1,4 @@ -{% extends 'snippets/components/dropdown.html' %} +{% extends 'components/dropdown.html' %} {% block dropdown-trigger %} Change shelf diff --git a/bookwyrm/templates/snippets/shelve_button_dropdown.html b/bookwyrm/templates/snippets/shelve_button_dropdown.html index 80c76d41..50140e3f 100644 --- a/bookwyrm/templates/snippets/shelve_button_dropdown.html +++ b/bookwyrm/templates/snippets/shelve_button_dropdown.html @@ -1,4 +1,4 @@ -{% extends 'snippets/components/dropdown.html' %} +{% extends 'components/dropdown.html' %} {% block dropdown-trigger %} More shelves diff --git a/bookwyrm/templates/snippets/start_reading_modal.html b/bookwyrm/templates/snippets/start_reading_modal.html index 50724e11..239a2be1 100644 --- a/bookwyrm/templates/snippets/start_reading_modal.html +++ b/bookwyrm/templates/snippets/start_reading_modal.html @@ -1,4 +1,4 @@ -{% extends 'snippets/components/modal.html' %} +{% extends 'components/modal.html' %} {% block modal-title %} Start "{{ book.title }}" diff --git a/bookwyrm/templates/snippets/status_body.html b/bookwyrm/templates/snippets/status_body.html index c70f898a..69fa0bc2 100644 --- a/bookwyrm/templates/snippets/status_body.html +++ b/bookwyrm/templates/snippets/status_body.html @@ -1,4 +1,4 @@ -{% extends 'snippets/components/card.html' %} +{% extends 'components/card.html' %} {% load bookwyrm_tags %} {% load humanize %} diff --git a/bookwyrm/templates/snippets/status_options.html b/bookwyrm/templates/snippets/status_options.html index b5887b1d..a6609cb3 100644 --- a/bookwyrm/templates/snippets/status_options.html +++ b/bookwyrm/templates/snippets/status_options.html @@ -1,4 +1,4 @@ -{% extends 'snippets/components/dropdown.html' %} +{% extends 'components/dropdown.html' %} {% load bookwyrm_tags %} {% block dropdown-trigger %} diff --git a/bookwyrm/templates/snippets/user_options.html b/bookwyrm/templates/snippets/user_options.html index 2c163034..ab68c2e3 100644 --- a/bookwyrm/templates/snippets/user_options.html +++ b/bookwyrm/templates/snippets/user_options.html @@ -1,4 +1,4 @@ -{% extends 'snippets/components/dropdown.html' %} +{% extends 'components/dropdown.html' %} {% load bookwyrm_tags %} {% block dropdown-trigger %} diff --git a/bookwyrm/templates/status.html b/bookwyrm/templates/status.html deleted file mode 100644 index d38ed89e..00000000 --- a/bookwyrm/templates/status.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends 'layout.html' %} -{% block content %} - -
    - {% include 'snippets/thread.html' with status=status depth=0 max_depth=6 is_root=True direction=0 %} -
    - -{% endblock %} - diff --git a/bookwyrm/templates/followers.html b/bookwyrm/templates/user/followers.html similarity index 69% rename from bookwyrm/templates/followers.html rename to bookwyrm/templates/user/followers.html index 00cb13ca..42b8cfb0 100644 --- a/bookwyrm/templates/followers.html +++ b/bookwyrm/templates/user/followers.html @@ -1,18 +1,17 @@ -{% extends 'layout.html' %} +{% extends 'user/user_layout.html' %} {% load bookwyrm_tags %} -{% block content %} -
    -

    - {% if is_self %}Your - {% else %} - {% include 'snippets/username.html' with user=user possessive=True %} - {% endif %} - followers -

    -
    -{% include 'snippets/user_header.html' with user=user %} +{% block header %} +

    + {% if is_self %}Your + {% else %} + {% include 'snippets/username.html' with user=user possessive=True %} + {% endif %} + followers +

    +{% endblock %} +{% block panel %}

    Followers

    {% for followers in followers %} @@ -34,5 +33,4 @@
    {{ user|username }} has no followers
    {% endif %}
    - {% endblock %} diff --git a/bookwyrm/templates/following.html b/bookwyrm/templates/user/following.html similarity index 70% rename from bookwyrm/templates/following.html rename to bookwyrm/templates/user/following.html index bdf02c74..9e42b783 100644 --- a/bookwyrm/templates/following.html +++ b/bookwyrm/templates/user/following.html @@ -1,18 +1,17 @@ -{% extends 'layout.html' %} +{% extends 'user/user_layout.html' %} {% load bookwyrm_tags %} -{% block content %} -
    -

    - Users following - {% if is_self %}you - {% else %} - {% include 'snippets/username.html' with user=user %} - {% endif %} -

    -
    -{% include 'snippets/user_header.html' with user=user %} +{% block header %} +

    + Users following + {% if is_self %}you + {% else %} + {% include 'snippets/username.html' with user=user %} + {% endif %} +

    +{% endblock %} +{% block panel %}

    Following

    {% for follower in user.following.all %} @@ -34,5 +33,4 @@
    {{ user|username }} isn't following any users
    {% endif %}
    - {% endblock %} diff --git a/bookwyrm/templates/user.html b/bookwyrm/templates/user/user.html similarity index 93% rename from bookwyrm/templates/user.html rename to bookwyrm/templates/user/user.html index 69b762b0..48f15398 100644 --- a/bookwyrm/templates/user.html +++ b/bookwyrm/templates/user/user.html @@ -1,13 +1,13 @@ -{% extends 'layout.html' %} -{% block content %} +{% extends 'user/user_layout.html' %} +{% block header %} +{% endblock %} -{% include 'snippets/user_header.html' with user=user %} +{% block panel %} {% if user.bookwyrm_user %}

    Shelves

    diff --git a/bookwyrm/templates/snippets/user_header.html b/bookwyrm/templates/user/user_layout.html similarity index 92% rename from bookwyrm/templates/snippets/user_header.html rename to bookwyrm/templates/user/user_layout.html index 8f5e264a..3fcbf1e2 100644 --- a/bookwyrm/templates/snippets/user_header.html +++ b/bookwyrm/templates/user/user_layout.html @@ -1,5 +1,13 @@ +{% extends 'layout.html' %} {% load humanize %} {% load bookwyrm_tags %} + +{% block content %} +
    + {% block header %}{% endblock %} +
    + +{# user bio #}
    @@ -60,3 +68,6 @@ {% endif %}
    +{% block panel %}{% endblock %} + +{% endblock %} diff --git a/bookwyrm/tests/views/test_block.py b/bookwyrm/tests/views/test_block.py index fa5254d1..f3a0c2f8 100644 --- a/bookwyrm/tests/views/test_block.py +++ b/bookwyrm/tests/views/test_block.py @@ -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): diff --git a/bookwyrm/tests/views/test_direct_message.py b/bookwyrm/tests/views/test_direct_message.py deleted file mode 100644 index 48820c75..00000000 --- a/bookwyrm/tests/views/test_direct_message.py +++ /dev/null @@ -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) diff --git a/bookwyrm/tests/views/test_feed.py b/bookwyrm/tests/views/test_feed.py new file mode 100644 index 00000000..da597a7c --- /dev/null +++ b/bookwyrm/tests/views/test_feed.py @@ -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) diff --git a/bookwyrm/tests/views/test_landing.py b/bookwyrm/tests/views/test_landing.py index 5596b4f3..8576af49 100644 --- a/bookwyrm/tests/views/test_landing.py +++ b/bookwyrm/tests/views/test_landing.py @@ -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) diff --git a/bookwyrm/tests/views/test_password.py b/bookwyrm/tests/views/test_password.py index 0f9c8988..8cac2b13 100644 --- a/bookwyrm/tests/views/test_password.py +++ b/bookwyrm/tests/views/test_password.py @@ -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) diff --git a/bookwyrm/tests/views/test_status.py b/bookwyrm/tests/views/test_status.py index 50c458ac..3be81c48 100644 --- a/bookwyrm/tests/views/test_status.py +++ b/bookwyrm/tests/views/test_status.py @@ -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() diff --git a/bookwyrm/tests/views/test_user.py b/bookwyrm/tests/views/test_user.py index 07acc4c4..61fcdb64 100644 --- a/bookwyrm/tests/views/test_user.py +++ b/bookwyrm/tests/views/test_user.py @@ -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) diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index f03bcda9..d232747f 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -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%s)' % regex.username @@ -49,7 +48,6 @@ urlpatterns = [ re_path(r'^password-reset/?$', views.PasswordResetRequest.as_view()), re_path(r'^password-reset/(?P[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'^(?Phome|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'^(?Phome|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\d+)/?$', views.Block.as_view()), + re_path(r'^unblock/(?P\d+)/?$', views.unblock), + # reading goals re_path(r'%s/goal/(?P\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\d+)/?$', views.Block.as_view()), - re_path(r'^unblock/(?P\d+)/?$', views.unblock), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index e3ac29c8..80520eb3 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -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 diff --git a/bookwyrm/views/block.py b/bookwyrm/views/block.py index fb95479a..ebcced2a 100644 --- a/bookwyrm/views/block.py +++ b/bookwyrm/views/block.py @@ -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') diff --git a/bookwyrm/views/direct_message.py b/bookwyrm/views/direct_message.py deleted file mode 100644 index 1f6c4f19..00000000 --- a/bookwyrm/views/direct_message.py +++ /dev/null @@ -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) diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py new file mode 100644 index 00000000..931cf355 --- /dev/null +++ b/bookwyrm/views/feed.py @@ -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 diff --git a/bookwyrm/views/landing.py b/bookwyrm/views/landing.py index ec6cb3a9..0d841ef0 100644 --- a/bookwyrm/views/landing.py +++ b/bookwyrm/views/landing.py @@ -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 diff --git a/bookwyrm/views/password.py b/bookwyrm/views/password.py index 06ddc1da..2adeebf5 100644 --- a/bookwyrm/views/password.py +++ b/bookwyrm/views/password.py @@ -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) diff --git a/bookwyrm/views/rss_feed.py b/bookwyrm/views/rss_feed.py index d6bcd174..496689ff 100644 --- a/bookwyrm/views/rss_feed.py +++ b/bookwyrm/views/rss_feed.py @@ -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 - diff --git a/bookwyrm/views/status.py b/bookwyrm/views/status.py index 834cf583..4d342bfb 100644 --- a/bookwyrm/views/status.py +++ b/bookwyrm/views/status.py @@ -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): diff --git a/bookwyrm/views/user.py b/bookwyrm/views/user.py index 4afda01b..a06face2 100644 --- a/bookwyrm/views/user.py +++ b/bookwyrm/views/user.py @@ -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)