From 930d9429efa0138da7625a5270b4eba3ead890ec Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 10 Feb 2021 12:43:53 -0800 Subject: [PATCH 01/34] User save() override instead of signal to set user fields this gets gnarly because of transaction.atomic, so it bears further testing --- bookwyrm/models/user.py | 80 ++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 42 deletions(-) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index da717d2e..7b0f9a91 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -6,7 +6,6 @@ from django.apps import apps from django.contrib.auth.models import AbstractUser from django.core.validators import MinValueValidator from django.db import models -from django.dispatch import receiver from django.utils import timezone from bookwyrm import activitypub @@ -172,15 +171,23 @@ class User(OrderedCollectionPageMixin, AbstractUser): def save(self, *args, **kwargs): ''' populate fields for new local users ''' - # this user already exists, no need to populate fields if not self.local and not re.match(regex.full_username, self.username): # generate a username that uses the domain (webfinger format) actor_parts = urlparse(self.remote_id) self.username = '%s@%s' % (self.username, actor_parts.netloc) - return super().save(*args, **kwargs) + super().save(*args, **kwargs) + return - if self.id or not self.local: - return super().save(*args, **kwargs) + # this user already exists, no need to populate fields + if self.id: + super().save(*args, **kwargs) + return + + # this is a new remote user, we need to set their remote server field + if not self.local: + super().save(*args, **kwargs) + set_remote_server.delay(self.id) + return # populate fields for local users self.remote_id = 'https://%s/user/%s' % (DOMAIN, self.localname) @@ -188,7 +195,32 @@ class User(OrderedCollectionPageMixin, AbstractUser): self.shared_inbox = 'https://%s/inbox' % DOMAIN self.outbox = '%s/outbox' % self.remote_id - return super().save(*args, **kwargs) + # an id needs to be set before we can proceed with related models + super().save(*args, **kwargs) + + # create keys and shelves for new local users + self.key_pair = KeyPair.objects.create( + remote_id='%s/#main-key' % self.remote_id) + self.save(broadcast=False) + + shelves = [{ + 'name': 'To Read', + 'identifier': 'to-read', + }, { + 'name': 'Currently Reading', + 'identifier': 'reading', + }, { + 'name': 'Read', + 'identifier': 'read', + }] + + for shelf in shelves: + Shelf( + name=shelf['name'], + identifier=shelf['identifier'], + user=self, + editable=False + ).save(broadcast=False) @property def local_path(self): @@ -280,42 +312,6 @@ class AnnualGoal(BookWyrmModel): finish_date__year__gte=self.year).count() - -@receiver(models.signals.post_save, sender=User) -#pylint: disable=unused-argument -def execute_after_save(sender, instance, created, *args, **kwargs): - ''' create shelves for new users ''' - if not created: - return - - if not instance.local: - set_remote_server.delay(instance.id) - return - - instance.key_pair = KeyPair.objects.create( - remote_id='%s/#main-key' % instance.remote_id) - instance.save(broadcast=False) - - shelves = [{ - 'name': 'To Read', - 'identifier': 'to-read', - }, { - 'name': 'Currently Reading', - 'identifier': 'reading', - }, { - 'name': 'Read', - 'identifier': 'read', - }] - - for shelf in shelves: - Shelf( - name=shelf['name'], - identifier=shelf['identifier'], - user=instance, - editable=False - ).save(broadcast=False) - - @app.task def set_remote_server(user_id): ''' figure out the user's remote server in the background ''' From 65f81bd5f0fb5c6075ba5bb39b008066c6ae11c3 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 15 Feb 2021 12:21:48 -0800 Subject: [PATCH 02/34] Moves blocking to save function I just like these better than signals?? --- bookwyrm/incoming.py | 2 +- bookwyrm/models/relationship.py | 28 +++++++++++----------------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/bookwyrm/incoming.py b/bookwyrm/incoming.py index 562225e7..cb5baafc 100644 --- a/bookwyrm/incoming.py +++ b/bookwyrm/incoming.py @@ -195,7 +195,7 @@ def handle_block(activity): ''' blocking a user ''' # create "block" databse entry activitypub.Block(**activity).to_model(models.UserBlocks) - # the removing relationships is handled in post-save hook in model + # the removing relationships is handled in model save @app.task diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index 9f3bf07d..6aaffae9 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -1,7 +1,6 @@ ''' defines relationships between users ''' from django.db import models, transaction from django.db.models import Q -from django.dispatch import receiver from bookwyrm import activitypub from .activitypub_mixin import ActivitypubMixin, ActivityMixin @@ -126,20 +125,15 @@ class UserBlocks(ActivityMixin, UserRelationship): status = 'blocks' activity_serializer = activitypub.Block + def save(self, *args, **kwargs): + ''' remove follow or follow request rels after a block is created ''' + super().save(*args, **kwargs) -@receiver(models.signals.post_save, sender=UserBlocks) -#pylint: disable=unused-argument -def execute_after_save(sender, instance, created, *args, **kwargs): - ''' remove follow or follow request rels after a block is created ''' - UserFollows.objects.filter( - Q(user_subject=instance.user_subject, - user_object=instance.user_object) | \ - Q(user_subject=instance.user_object, - user_object=instance.user_subject) - ).delete() - UserFollowRequest.objects.filter( - Q(user_subject=instance.user_subject, - user_object=instance.user_object) | \ - Q(user_subject=instance.user_object, - user_object=instance.user_subject) - ).delete() + UserFollows.objects.filter( + Q(user_subject=self.user_subject, user_object=self.user_object) | \ + Q(user_subject=self.user_object, user_object=self.user_subject) + ).delete() + UserFollowRequest.objects.filter( + Q(user_subject=self.user_subject, user_object=self.user_object) | \ + Q(user_subject=self.user_object, user_object=self.user_subject) + ).delete() From f974b9b89538f226a30c4854fa3947f51ff05e28 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 15 Feb 2021 12:51:34 -0800 Subject: [PATCH 03/34] Better blocking checks --- bookwyrm/models/relationship.py | 48 ++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index 044b08ac..f4df3b83 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -1,6 +1,6 @@ ''' defines relationships between users ''' from django.apps import apps -from django.db import models, transaction +from django.db import models, transaction, IntegrityError from django.db.models import Q from bookwyrm import activitypub @@ -60,6 +60,20 @@ class UserFollows(ActivitypubMixin, UserRelationship): status = 'follows' activity_serializer = activitypub.Follow + def save(self, *args, **kwargs): + ''' really really don't let a user follow someone who blocked them ''' + # blocking in either direction is a no-go + if UserBlocks.objects.filter( + Q( + user_subject=self.user_subject, + user_object=self.user_object, + ) | Q( + user_subject=self.user_object, + user_object=self.user_subject, + ) + ).exists(): + raise IntegrityError() + super().save(*args, **kwargs) @classmethod def from_request(cls, follow_request): @@ -78,23 +92,25 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship): def save(self, *args, broadcast=True, **kwargs): ''' make sure the follow or block relationship doesn't already exist ''' - try: - UserFollows.objects.get( + # don't create a request if a follow already exists + if UserFollows.objects.filter( user_subject=self.user_subject, user_object=self.user_object, - ) - # blocking in either direction is a no-go - UserBlocks.objects.get( - user_subject=self.user_subject, - user_object=self.user_object, - ) - UserBlocks.objects.get( - user_subject=self.user_object, - user_object=self.user_subject, - ) - return None - except (UserFollows.DoesNotExist, UserBlocks.DoesNotExist): - super().save(*args, **kwargs) + ).exists(): + raise IntegrityError() + # blocking in either direction is a no-go + if UserBlocks.objects.filter( + Q( + user_subject=self.user_subject, + user_object=self.user_object, + ) | Q( + user_subject=self.user_object, + user_object=self.user_subject, + ) + ).exists(): + raise IntegrityError() + + super().save(*args, **kwargs) if broadcast and self.user_subject.local and not self.user_object.local: self.broadcast(self.to_activity(), self.user_subject) From 02612b00e912564ebd436039138addcae353b840 Mon Sep 17 00:00:00 2001 From: Jim Fingal Date: Sun, 21 Feb 2021 22:49:52 -0800 Subject: [PATCH 04/34] Update Readme --- README.md | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 6a624ab3..c8b6e92b 100644 --- a/README.md +++ b/README.md @@ -112,36 +112,42 @@ Once the build is complete, you can access the instance at `localhost:1333` ## Installing in Production This project is still young and isn't, at the momoment, very stable, so please procede with caution when running in production. + ### Server setup - Get a domain name and set up DNS for your server - Set your server up with appropriate firewalls for running a web application (this instruction set is tested again Ubuntu 20.04) - - Set up a mailgun account and the appropriate DNS settings + - Set up an email service (such as mailgun) and the appropriate SMTP/DNS settings - Install Docker and docker-compose + ### Install and configure BookWyrm + +The `production` branch of BookWyrm contains a number of tools not on the `main` branch that are suited for running in production, such as `docker-compose` changes to update the default commands or configuration of containers, and indivudal changes to container config to enable things like SSL or regular backups. + +Instructions for running BookWyrm in production: + - Get the application code: `git clone git@github.com:mouse-reeve/bookwyrm.git` - Switch to the `production` branch `git checkout production` - Create your environment variables file `cp .env.example .env` - - Add your domain, email address, mailgun credentials + - Add your domain, email address, SMTP credentials - Set a secure redis password and secret key - Set a secure database password for postgres - Update your nginx configuration in `nginx/default.conf` - Replace `your-domain.com` with your domain name - - Run the application (this should also set up a Certbot ssl cert for your domain) - `docker-compose up --build` - Make sure all the images build successfully + - Run the application (this should also set up a Certbot ssl cert for your domain) with + `docker-compose up --build`, and make sure all the images build successfully - When docker has built successfully, stop the process with `CTRL-C` - Comment out the `command: certonly...` line in `docker-compose.yml` - - Run docker-compose in the background - `docker-compose up -d` - - Initialize the database - `./bw-dev initdb` - - Set up schedule backups with cron that runs that `docker-compose exec db pg_dump -U ` and saves the backup to a safe locationgi - - Congrats! You did it, go to your domain and enjoy the fruits of your labors + - Run docker-compose in the background with: `docker-compose up -d` + - Initialize the database with: `./bw-dev initdb` + - Set up schedule backups with cron that runs that `docker-compose exec db pg_dump -U ` and saves the backup to a safe location + +Congrats! You did it, go to your domain and enjoy the fruits of your labors. + ### Configure your instance - - Register a user account in the applcation UI + - Register a user account in the application UI - Make your account a superuser (warning: do *not* use django's `createsuperuser` command) - On your server, open the django shell `./bw-dev shell` From f654444aab261ad52e5c973ca883e4797a0a0446 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 22 Feb 2021 08:53:01 -0800 Subject: [PATCH 05/34] Fixes bug in saving remote server --- bookwyrm/models/user.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 7b0f9a91..170b587e 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -171,15 +171,15 @@ class User(OrderedCollectionPageMixin, AbstractUser): def save(self, *args, **kwargs): ''' populate fields for new local users ''' + created = not bool(self.id) if not self.local and not re.match(regex.full_username, self.username): # generate a username that uses the domain (webfinger format) actor_parts = urlparse(self.remote_id) self.username = '%s@%s' % (self.username, actor_parts.netloc) super().save(*args, **kwargs) - return # this user already exists, no need to populate fields - if self.id: + if created: super().save(*args, **kwargs) return From bff75cedf54dd0e5dd5d30aa9848406c98a9ff40 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 22 Feb 2021 09:41:52 -0800 Subject: [PATCH 06/34] Boolean error in user save causing infinite recursion --- bookwyrm/models/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 170b587e..a65317fa 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -179,7 +179,7 @@ class User(OrderedCollectionPageMixin, AbstractUser): super().save(*args, **kwargs) # this user already exists, no need to populate fields - if created: + if not created: super().save(*args, **kwargs) return From 6b74f56381cdf1c58c9cb0fc468896a535dc3071 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 22 Feb 2021 10:01:19 -0800 Subject: [PATCH 07/34] Safer set remote server --- bookwyrm/models/user.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index a65317fa..5e77b9e1 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -9,7 +9,7 @@ from django.db import models from django.utils import timezone from bookwyrm import activitypub -from bookwyrm.connectors import get_data +from bookwyrm.connectors import get_data, ConnectorException from bookwyrm.models.shelf import Shelf from bookwyrm.models.status import Status, Review from bookwyrm.settings import DOMAIN @@ -333,19 +333,24 @@ def get_or_create_remote_server(domain): except FederatedServer.DoesNotExist: pass - data = get_data('https://%s/.well-known/nodeinfo' % domain) - try: - nodeinfo_url = data.get('links')[0].get('href') - except (TypeError, KeyError): - return None + data = get_data('https://%s/.well-known/nodeinfo' % domain) + try: + nodeinfo_url = data.get('links')[0].get('href') + except (TypeError, KeyError): + return None + + data = get_data(nodeinfo_url) + application_type = data.get('software', {}).get('name') + application_type = data.get('software', {}).get('version') + except ConnectorException: + application_type = application_version = None - data = get_data(nodeinfo_url) server = FederatedServer.objects.create( server_name=domain, - application_type=data['software']['name'], - application_version=data['software']['version'], + application_type=application_type, + application_version=application_version, ) return server From ef9acaf87810a999ab01577764ba5400f0c6cd51 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 22 Feb 2021 11:38:11 -0800 Subject: [PATCH 08/34] Adds tests for setting remote server --- bookwyrm/models/user.py | 4 +- bookwyrm/tests/models/test_user_model.py | 84 +++++++++++++++++++++++- 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 5e77b9e1..a1f927b2 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -338,11 +338,11 @@ def get_or_create_remote_server(domain): try: nodeinfo_url = data.get('links')[0].get('href') except (TypeError, KeyError): - return None + raise ConnectorException() data = get_data(nodeinfo_url) application_type = data.get('software', {}).get('name') - application_type = data.get('software', {}).get('version') + application_version = data.get('software', {}).get('version') except ConnectorException: application_type = application_version = None diff --git a/bookwyrm/tests/models/test_user_model.py b/bookwyrm/tests/models/test_user_model.py index a10c89b8..bd184b65 100644 --- a/bookwyrm/tests/models/test_user_model.py +++ b/bookwyrm/tests/models/test_user_model.py @@ -1,11 +1,13 @@ ''' testing models ''' from unittest.mock import patch from django.test import TestCase +import responses from bookwyrm import models from bookwyrm.settings import DOMAIN - +# pylint: disable=missing-class-docstring +# pylint: disable=missing-function-docstring class User(TestCase): def setUp(self): self.user = models.User.objects.create_user( @@ -71,3 +73,83 @@ class User(TestCase): self.assertEqual(activity['type'], 'OrderedCollection') self.assertEqual(activity['id'], self.user.outbox) self.assertEqual(activity['totalItems'], 0) + + + def test_set_remote_server(self): + server = models.FederatedServer.objects.create( + server_name=DOMAIN, + application_type='test type', + application_version=3 + ) + + models.user.set_remote_server(self.user.id) + self.user.refresh_from_db() + + self.assertEqual(self.user.federated_server, server) + + @responses.activate + def test_get_or_create_remote_server(self): + responses.add( + responses.GET, + 'https://%s/.well-known/nodeinfo' % DOMAIN, + json={'links': [{'href': 'http://www.example.com'}, {}]} + ) + responses.add( + responses.GET, + 'http://www.example.com', + json={'software': {'name': 'hi', 'version': '2'}}, + ) + + server = models.user.get_or_create_remote_server(DOMAIN) + self.assertEqual(server.server_name, DOMAIN) + self.assertEqual(server.application_type, 'hi') + self.assertEqual(server.application_version, '2') + + @responses.activate + def test_get_or_create_remote_server_no_wellknown(self): + responses.add( + responses.GET, + 'https://%s/.well-known/nodeinfo' % DOMAIN, + status=404 + ) + + server = models.user.get_or_create_remote_server(DOMAIN) + self.assertEqual(server.server_name, DOMAIN) + self.assertIsNone(server.application_type) + self.assertIsNone(server.application_version) + + @responses.activate + def test_get_or_create_remote_server_no_links(self): + responses.add( + responses.GET, + 'https://%s/.well-known/nodeinfo' % DOMAIN, + json={'links': [{'href': 'http://www.example.com'}, {}]} + ) + responses.add( + responses.GET, + 'http://www.example.com', + status=404 + ) + + server = models.user.get_or_create_remote_server(DOMAIN) + self.assertEqual(server.server_name, DOMAIN) + self.assertIsNone(server.application_type) + self.assertIsNone(server.application_version) + + @responses.activate + def test_get_or_create_remote_server_unknown_format(self): + responses.add( + responses.GET, + 'https://%s/.well-known/nodeinfo' % DOMAIN, + json={'links': [{'href': 'http://www.example.com'}, {}]} + ) + responses.add( + responses.GET, + 'http://www.example.com', + json={'fish': 'salmon'} + ) + + server = models.user.get_or_create_remote_server(DOMAIN) + self.assertEqual(server.server_name, DOMAIN) + self.assertIsNone(server.application_type) + self.assertIsNone(server.application_version) From 726a8739a36f5927bc9ac0c331fa3a6ebd75342c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 22 Feb 2021 11:42:45 -0800 Subject: [PATCH 09/34] way too much logging coming from http errors --- bookwyrm/connectors/abstract_connector.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/bookwyrm/connectors/abstract_connector.py b/bookwyrm/connectors/abstract_connector.py index 527d2f42..5ccd9f91 100644 --- a/bookwyrm/connectors/abstract_connector.py +++ b/bookwyrm/connectors/abstract_connector.py @@ -216,11 +216,7 @@ def get_data(url): raise ConnectorException() if not resp.ok: - try: - resp.raise_for_status() - except requests.exceptions.HTTPError as e: - logger.exception(e) - raise ConnectorException() + raise ConnectorException() try: data = resp.json() except ValueError as e: From 6e09d485c4395a970a3ee28c106973c5dc4658bc Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 11:34:15 -0800 Subject: [PATCH 10/34] Outbox sensitive to user agent strings --- bookwyrm/models/activitypub_mixin.py | 6 ++--- bookwyrm/tests/views/test_helpers.py | 6 ++--- bookwyrm/tests/views/test_outbox.py | 37 ++++++++++++++++++++++++++++ bookwyrm/views/feed.py | 4 +-- bookwyrm/views/helpers.py | 4 +-- bookwyrm/views/outbox.py | 7 +++++- 6 files changed, 53 insertions(+), 11 deletions(-) diff --git a/bookwyrm/models/activitypub_mixin.py b/bookwyrm/models/activitypub_mixin.py index 84293725..d934681e 100644 --- a/bookwyrm/models/activitypub_mixin.py +++ b/bookwyrm/models/activitypub_mixin.py @@ -282,7 +282,7 @@ class OrderedCollectionPageMixin(ObjectMixin): def to_ordered_collection(self, queryset, \ remote_id=None, page=False, collection_only=False, **kwargs): - ''' an ordered collection of whatevers ''' + 'pure=pure, '' an ordered collection of whatevers ''' if not queryset.ordered: raise RuntimeError('queryset must be ordered') @@ -472,7 +472,7 @@ def sign_and_send(sender, data, destination): # pylint: disable=unused-argument def to_ordered_collection_page( - queryset, remote_id, id_only=False, page=1, **kwargs): + queryset, remote_id, id_only=False, page=1, pure=False, **kwargs): ''' serialize and pagiante a queryset ''' paginated = Paginator(queryset, PAGE_LENGTH) @@ -480,7 +480,7 @@ def to_ordered_collection_page( if id_only: items = [s.remote_id for s in activity_page.object_list] else: - items = [s.to_activity() for s in activity_page.object_list] + items = [s.to_activity(pure=pure) for s in activity_page.object_list] prev_page = next_page = None if activity_page.has_next(): diff --git a/bookwyrm/tests/views/test_helpers.py b/bookwyrm/tests/views/test_helpers.py index b75d61d5..b3a79b32 100644 --- a/bookwyrm/tests/views/test_helpers.py +++ b/bookwyrm/tests/views/test_helpers.py @@ -188,18 +188,18 @@ class ViewsHelpers(TestCase): def test_is_bookwyrm_request(self): ''' checks if a request came from a bookwyrm instance ''' request = self.factory.get('', {'q': 'Test Book'}) - self.assertFalse(views.helpers.is_bookworm_request(request)) + self.assertFalse(views.helpers.is_bookwyrm_request(request)) request = self.factory.get( '', {'q': 'Test Book'}, HTTP_USER_AGENT=\ "http.rb/4.4.1 (Mastodon/3.3.0; +https://mastodon.social/)" ) - self.assertFalse(views.helpers.is_bookworm_request(request)) + self.assertFalse(views.helpers.is_bookwyrm_request(request)) request = self.factory.get( '', {'q': 'Test Book'}, HTTP_USER_AGENT=USER_AGENT) - self.assertTrue(views.helpers.is_bookworm_request(request)) + self.assertTrue(views.helpers.is_bookwyrm_request(request)) def test_existing_user(self): diff --git a/bookwyrm/tests/views/test_outbox.py b/bookwyrm/tests/views/test_outbox.py index d59f028c..7986dea6 100644 --- a/bookwyrm/tests/views/test_outbox.py +++ b/bookwyrm/tests/views/test_outbox.py @@ -7,6 +7,7 @@ from django.test import TestCase from django.test.client import RequestFactory from bookwyrm import models, views +from bookwyrm.settings import USER_AGENT # pylint: disable=too-many-public-methods @@ -90,3 +91,39 @@ class OutboxView(TestCase): data = json.loads(result.content) self.assertEqual(data['type'], 'OrderedCollection') self.assertEqual(data['totalItems'], 1) + + def test_outbox_bookwyrm_request_true(self): + ''' should differentiate between bookwyrm and outside requests ''' + with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + models.Review.objects.create( + name='hi', + content='look at this', + user=self.local_user, + book=self.book, + privacy='public', + ) + + request = self.factory.get('', {'page': 1}, HTTP_USER_AGENT=USER_AGENT) + result = views.Outbox.as_view()(request, 'mouse') + + data = json.loads(result.content) + self.assertEqual(len(data['orderedItems']), 1) + self.assertEqual(data['orderedItems'][0]['type'], 'Review') + + def test_outbox_bookwyrm_request_false(self): + ''' should differentiate between bookwyrm and outside requests ''' + with patch('bookwyrm.models.activitypub_mixin.broadcast_task.delay'): + models.Review.objects.create( + name='hi', + content='look at this', + user=self.local_user, + book=self.book, + privacy='public', + ) + + request = self.factory.get('', {'page': 1}) + result = views.Outbox.as_view()(request, 'mouse') + + data = json.loads(result.content) + self.assertEqual(len(data['orderedItems']), 1) + self.assertEqual(data['orderedItems'][0]['type'], 'Article') diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py index 0e550f0c..059237b9 100644 --- a/bookwyrm/views/feed.py +++ b/bookwyrm/views/feed.py @@ -13,7 +13,7 @@ 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 +from .helpers import is_api_request, is_bookwyrm_request, object_visible_to_user # pylint: disable= no-self-use @@ -107,7 +107,7 @@ class Status(View): if is_api_request(request): return ActivitypubResponse( - status.to_activity(pure=not is_bookworm_request(request))) + status.to_activity(pure=not is_bookwyrm_request(request))) data = {**feed_page_data(request.user), **{ 'title': 'Status by %s' % user.username, diff --git a/bookwyrm/views/helpers.py b/bookwyrm/views/helpers.py index 842b8d1c..6eeaa926 100644 --- a/bookwyrm/views/helpers.py +++ b/bookwyrm/views/helpers.py @@ -24,8 +24,8 @@ def is_api_request(request): request.path[-5:] == '.json' -def is_bookworm_request(request): - ''' check if the request is coming from another bookworm instance ''' +def is_bookwyrm_request(request): + ''' check if the request is coming from another bookwyrm instance ''' user_agent = request.headers.get('User-Agent') if user_agent is None or \ re.search(regex.bookwyrm_user_agent, user_agent) is None: diff --git a/bookwyrm/views/outbox.py b/bookwyrm/views/outbox.py index 8bfc3b3d..5df9d199 100644 --- a/bookwyrm/views/outbox.py +++ b/bookwyrm/views/outbox.py @@ -4,6 +4,7 @@ from django.shortcuts import get_object_or_404 from django.views import View from bookwyrm import activitypub, models +from .helpers import is_bookwyrm_request # pylint: disable= no-self-use @@ -17,6 +18,10 @@ class Outbox(View): filter_type = None return JsonResponse( - user.to_outbox(**request.GET, filter_type=filter_type), + user.to_outbox( + **request.GET, + filter_type=filter_type, + pure=not is_bookwyrm_request(request) + ), encoder=activitypub.ActivityEncoder ) From c6a61abf79a2e51dcb4bed9c28245188326655ee Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 11:58:01 -0800 Subject: [PATCH 11/34] Don't try to fetch reviews for remote user in test --- bookwyrm/tests/models/test_user_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/tests/models/test_user_model.py b/bookwyrm/tests/models/test_user_model.py index bd184b65..34f4c54d 100644 --- a/bookwyrm/tests/models/test_user_model.py +++ b/bookwyrm/tests/models/test_user_model.py @@ -30,7 +30,7 @@ class User(TestCase): with patch('bookwyrm.models.user.set_remote_server.delay'): user = models.User.objects.create_user( 'rat', 'rat@rat.rat', 'ratword', local=False, - remote_id='https://example.com/dfjkg') + remote_id='https://example.com/dfjkg', bookwyrm_user=False) self.assertEqual(user.username, 'rat@example.com') From a617302006b1de642ca09df5379408c3340dc9bb Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 12:10:54 -0800 Subject: [PATCH 12/34] Cleans up display of follow/block/unfollow/unblock buttons --- .../templates/snippets/follow_button.html | 37 ++++++++++++------- bookwyrm/templates/user/user_layout.html | 9 +---- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/bookwyrm/templates/snippets/follow_button.html b/bookwyrm/templates/snippets/follow_button.html index 7db69191..f7286e67 100644 --- a/bookwyrm/templates/snippets/follow_button.html +++ b/bookwyrm/templates/snippets/follow_button.html @@ -5,20 +5,29 @@ Follow request already sent. +{% elif user in request.user.blocks.all %} +{% include 'snippets/block_button.html' %} {% else %} - - +
+
+ + +
+
+ {% include 'snippets/user_options.html' with user=user class="is-small" %} +
+
{% endif %} diff --git a/bookwyrm/templates/user/user_layout.html b/bookwyrm/templates/user/user_layout.html index 979ee0b0..9abc469a 100644 --- a/bookwyrm/templates/user/user_layout.html +++ b/bookwyrm/templates/user/user_layout.html @@ -43,14 +43,7 @@ {% if not is_self and request.user.is_authenticated %} -
-
- {% include 'snippets/follow_button.html' with user=user %} -
-
- {% include 'snippets/user_options.html' with user=user class="is-small" %} -
-
+ {% include 'snippets/follow_button.html' with user=user %} {% endif %} {% if is_self and user.follower_requests.all %} From 364b053d9addf6962e4e7e6d8294790f9d30b64e Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 12:41:37 -0800 Subject: [PATCH 13/34] Better user block privacy --- bookwyrm/models/user.py | 10 ++++++++++ bookwyrm/tests/views/test_helpers.py | 8 +++++--- bookwyrm/views/feed.py | 2 +- bookwyrm/views/follow.py | 8 ++++---- bookwyrm/views/helpers.py | 6 +++--- bookwyrm/views/search.py | 2 +- bookwyrm/views/shelf.py | 2 +- bookwyrm/views/user.py | 6 +++--- 8 files changed, 28 insertions(+), 16 deletions(-) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index a65317fa..108f4345 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -112,6 +112,16 @@ class User(OrderedCollectionPageMixin, AbstractUser): activity_serializer = activitypub.Person + @classmethod + def viewer_aware_objects(cls, viewer): + ''' the user queryset filtered for the context of the logged in user ''' + queryset = cls.objects.filter(is_active=True) + if viewer.is_authenticated: + queryset = queryset.exclude( + blocks=viewer + ) + return queryset + def to_outbox(self, filter_type=None, **kwargs): ''' an ordered collection of statuses ''' if filter_type: diff --git a/bookwyrm/tests/views/test_helpers.py b/bookwyrm/tests/views/test_helpers.py index b75d61d5..577b45e5 100644 --- a/bookwyrm/tests/views/test_helpers.py +++ b/bookwyrm/tests/views/test_helpers.py @@ -56,12 +56,14 @@ class ViewsHelpers(TestCase): def test_get_user_from_username(self): ''' works for either localname or username ''' self.assertEqual( - views.helpers.get_user_from_username('mouse'), self.local_user) + views.helpers.get_user_from_username( + self.local_user, 'mouse'), self.local_user) self.assertEqual( views.helpers.get_user_from_username( - 'mouse@local.com'), self.local_user) + self.local_user, 'mouse@local.com'), self.local_user) with self.assertRaises(models.User.DoesNotExist): - views.helpers.get_user_from_username('mojfse@example.com') + views.helpers.get_user_from_username( + self.local_user, 'mojfse@example.com') def test_is_api_request(self): diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py index 0e550f0c..e67a893d 100644 --- a/bookwyrm/views/feed.py +++ b/bookwyrm/views/feed.py @@ -65,7 +65,7 @@ class DirectMessage(View): user = None if username: try: - user = get_user_from_username(username) + user = get_user_from_username(request.user, username) except models.User.DoesNotExist: pass if user: diff --git a/bookwyrm/views/follow.py b/bookwyrm/views/follow.py index c59f2e6d..e1b1a0bb 100644 --- a/bookwyrm/views/follow.py +++ b/bookwyrm/views/follow.py @@ -13,7 +13,7 @@ def follow(request): ''' follow another user, here or abroad ''' username = request.POST['user'] try: - to_follow = get_user_from_username(username) + to_follow = get_user_from_username(request.user, username) except models.User.DoesNotExist: return HttpResponseBadRequest() @@ -33,7 +33,7 @@ def unfollow(request): ''' unfollow a user ''' username = request.POST['user'] try: - to_unfollow = get_user_from_username(username) + to_unfollow = get_user_from_username(request.user, username) except models.User.DoesNotExist: return HttpResponseBadRequest() @@ -52,7 +52,7 @@ def accept_follow_request(request): ''' a user accepts a follow request ''' username = request.POST['user'] try: - requester = get_user_from_username(username) + requester = get_user_from_username(request.user, username) except models.User.DoesNotExist: return HttpResponseBadRequest() @@ -75,7 +75,7 @@ def delete_follow_request(request): ''' a user rejects a follow request ''' username = request.POST['user'] try: - requester = get_user_from_username(username) + requester = get_user_from_username(request.user, username) except models.User.DoesNotExist: return HttpResponseBadRequest() diff --git a/bookwyrm/views/helpers.py b/bookwyrm/views/helpers.py index 842b8d1c..7e0550fb 100644 --- a/bookwyrm/views/helpers.py +++ b/bookwyrm/views/helpers.py @@ -9,13 +9,13 @@ from bookwyrm.status import create_generated_note from bookwyrm.utils import regex -def get_user_from_username(username): +def get_user_from_username(viewer, username): ''' helper function to resolve a localname or a username to a user ''' # raises DoesNotExist if user is now found try: - return models.User.objects.get(localname=username) + return models.User.viwer_aware_objects(viewer).get(localname=username) except models.User.DoesNotExist: - return models.User.objects.get(username=username) + return models.User.viewer_aware_objects(viewer).get(username=username) def is_api_request(request): diff --git a/bookwyrm/views/search.py b/bookwyrm/views/search.py index a4cd7337..98be166f 100644 --- a/bookwyrm/views/search.py +++ b/bookwyrm/views/search.py @@ -33,7 +33,7 @@ class Search(View): handle_remote_webfinger(query) # do a user search - user_results = models.User.objects.annotate( + user_results = models.User.viewer_aware_objects(request.user).annotate( similarity=Greatest( TrigramSimilarity('username', query), TrigramSimilarity('localname', query), diff --git a/bookwyrm/views/shelf.py b/bookwyrm/views/shelf.py index 02502ff6..70d3d1de 100644 --- a/bookwyrm/views/shelf.py +++ b/bookwyrm/views/shelf.py @@ -19,7 +19,7 @@ class Shelf(View): def get(self, request, username, shelf_identifier): ''' display a shelf ''' try: - user = get_user_from_username(username) + user = get_user_from_username(request.user, username) except models.User.DoesNotExist: return HttpResponseNotFound() diff --git a/bookwyrm/views/user.py b/bookwyrm/views/user.py index 4da0fdac..7a238ce7 100644 --- a/bookwyrm/views/user.py +++ b/bookwyrm/views/user.py @@ -26,7 +26,7 @@ class User(View): def get(self, request, username): ''' profile page for a user ''' try: - user = get_user_from_username(username) + user = get_user_from_username(request.user, username) except models.User.DoesNotExist: return HttpResponseNotFound() @@ -96,7 +96,7 @@ class Followers(View): def get(self, request, username): ''' list of followers ''' try: - user = get_user_from_username(username) + user = get_user_from_username(request.user, username) except models.User.DoesNotExist: return HttpResponseNotFound() @@ -121,7 +121,7 @@ class Following(View): def get(self, request, username): ''' list of followers ''' try: - user = get_user_from_username(username) + user = get_user_from_username(request.user, username) except models.User.DoesNotExist: return HttpResponseNotFound() From b1268b7db8032356ef7cfe8db6c5d68053ff58e0 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 12:44:25 -0800 Subject: [PATCH 14/34] Small covers too small on mobile --- bookwyrm/static/css/format.css | 3 --- 1 file changed, 3 deletions(-) diff --git a/bookwyrm/static/css/format.css b/bookwyrm/static/css/format.css index 50ce101e..9d4b3105 100644 --- a/bookwyrm/static/css/format.css +++ b/bookwyrm/static/css/format.css @@ -100,9 +100,6 @@ .cover-container.is-medium { height: 100px; } - .cover-container.is-small { - height: 70px; - } } .cover-container.is-medium .no-cover div { From d1a21b851a9072f65d7eae86c137f930ef88d4c2 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 12:46:41 -0800 Subject: [PATCH 15/34] Maintain list columns in mobile --- bookwyrm/templates/lists/list.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/templates/lists/list.html b/bookwyrm/templates/lists/list.html index 5f9baa03..c5aef606 100644 --- a/bookwyrm/templates/lists/list.html +++ b/bookwyrm/templates/lists/list.html @@ -19,7 +19,7 @@ {% for item in items %}
  • -
    +
    @@ -73,7 +73,7 @@ {% endif %} {% for book in suggested_books %} {% if book %} -
    +
    From 8a3d1a0bf2db971ce6005855d78c073a12d147d4 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 13:04:24 -0800 Subject: [PATCH 16/34] Fixes header wrap on mobile headers --- bookwyrm/templates/author.html | 2 +- bookwyrm/templates/book.html | 2 +- bookwyrm/templates/lists/list_layout.html | 2 +- bookwyrm/templates/lists/lists.html | 2 +- bookwyrm/templates/snippets/shelf.html | 2 ++ bookwyrm/templates/user/followers.html | 18 +++++++----------- bookwyrm/templates/user/following.html | 18 +++++++----------- bookwyrm/templates/user/lists.html | 2 +- bookwyrm/templates/user/shelf.html | 2 +- bookwyrm/templates/user/user.html | 4 ++-- 10 files changed, 24 insertions(+), 30 deletions(-) diff --git a/bookwyrm/templates/author.html b/bookwyrm/templates/author.html index a875ad78..9f2054d0 100644 --- a/bookwyrm/templates/author.html +++ b/bookwyrm/templates/author.html @@ -2,7 +2,7 @@ {% load bookwyrm_tags %} {% block content %}
    -
    +

    {{ author.name }}

    diff --git a/bookwyrm/templates/book.html b/bookwyrm/templates/book.html index 0bef2856..3a2a6aa7 100644 --- a/bookwyrm/templates/book.html +++ b/bookwyrm/templates/book.html @@ -4,7 +4,7 @@ {% block content %}
    -
    +

    {{ book.title }}{% if book.subtitle %}: diff --git a/bookwyrm/templates/lists/list_layout.html b/bookwyrm/templates/lists/list_layout.html index d3ff2c48..f5855f27 100644 --- a/bookwyrm/templates/lists/list_layout.html +++ b/bookwyrm/templates/lists/list_layout.html @@ -2,7 +2,7 @@ {% load bookwyrm_tags %} {% block content %} -
    +

    {{ list.name }} {% include 'snippets/privacy-icons.html' with item=list %}

    Created {% if list.curation != 'open' %} and curated{% endif %} by {% include 'snippets/username.html' with user=list.user %}

    diff --git a/bookwyrm/templates/lists/lists.html b/bookwyrm/templates/lists/lists.html index 71d37250..259d0820 100644 --- a/bookwyrm/templates/lists/lists.html +++ b/bookwyrm/templates/lists/lists.html @@ -5,7 +5,7 @@

    Lists

    {% if request.user.is_authenticated and not lists.has_previous %} -
    +

    Your lists

    diff --git a/bookwyrm/templates/snippets/shelf.html b/bookwyrm/templates/snippets/shelf.html index 006bb4ea..0e4e9a08 100644 --- a/bookwyrm/templates/snippets/shelf.html +++ b/bookwyrm/templates/snippets/shelf.html @@ -1,6 +1,7 @@ {% load humanize %} {% load bookwyrm_tags %} {% if books|length > 0 %} +
    @@ -74,6 +75,7 @@ {% endfor %}
    +
    {% else %}

    This shelf is empty.

    {% if shelf.editable %} diff --git a/bookwyrm/templates/user/followers.html b/bookwyrm/templates/user/followers.html index 42b8cfb0..da6fc320 100644 --- a/bookwyrm/templates/user/followers.html +++ b/bookwyrm/templates/user/followers.html @@ -15,17 +15,13 @@

    Followers

    {% for followers in followers %} -
    -
    -
    - {% include 'snippets/avatar.html' with user=followers %} -
    -
    - {% include 'snippets/username.html' with user=followers show_full=True %} -
    -
    - {% include 'snippets/follow_button.html' with user=followers %} -
    +
    +
    + {% include 'snippets/avatar.html' with user=followers %} + {% include 'snippets/username.html' with user=followers show_full=True %} +
    +
    + {% include 'snippets/follow_button.html' with user=followers %}
    {% endfor %} diff --git a/bookwyrm/templates/user/following.html b/bookwyrm/templates/user/following.html index 9e42b783..d734b0dc 100644 --- a/bookwyrm/templates/user/following.html +++ b/bookwyrm/templates/user/following.html @@ -15,17 +15,13 @@

    Following

    {% for follower in user.following.all %} -
    -
    -
    - {% include 'snippets/avatar.html' with user=follower %} -
    -
    - {% include 'snippets/username.html' with user=follower show_full=True %} -
    -
    - {% include 'snippets/follow_button.html' with user=follower %} -
    +
    +
    + {% include 'snippets/avatar.html' with user=follower %} + {% include 'snippets/username.html' with user=follower show_full=True %} +
    +
    + {% include 'snippets/follow_button.html' with user=follower %}
    {% endfor %} diff --git a/bookwyrm/templates/user/lists.html b/bookwyrm/templates/user/lists.html index a006c92b..45e4806f 100644 --- a/bookwyrm/templates/user/lists.html +++ b/bookwyrm/templates/user/lists.html @@ -1,7 +1,7 @@ {% extends 'user/user_layout.html' %} {% block header %} -
    +

    {% if is_self %}Your diff --git a/bookwyrm/templates/user/shelf.html b/bookwyrm/templates/user/shelf.html index 5e2f532f..e62e2218 100644 --- a/bookwyrm/templates/user/shelf.html +++ b/bookwyrm/templates/user/shelf.html @@ -38,7 +38,7 @@ {% include 'user/create_shelf_form.html' with controls_text='create-shelf-form' %}

    -
    +

    {{ shelf.name }} diff --git a/bookwyrm/templates/user/user.html b/bookwyrm/templates/user/user.html index c7f6d3b5..2ff66468 100644 --- a/bookwyrm/templates/user/user.html +++ b/bookwyrm/templates/user/user.html @@ -1,7 +1,7 @@ {% extends 'user/user_layout.html' %} {% block header %} -
    +

    User profile

    @@ -54,7 +54,7 @@ {% endif %}
    -
    +

    User Activity

    From e6b4212e6b68b2860069b2e369b6f919dcc0c9b7 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 13:05:43 -0800 Subject: [PATCH 17/34] Typo fix --- bookwyrm/views/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/views/helpers.py b/bookwyrm/views/helpers.py index 7e0550fb..c7eb007f 100644 --- a/bookwyrm/views/helpers.py +++ b/bookwyrm/views/helpers.py @@ -13,7 +13,7 @@ def get_user_from_username(viewer, username): ''' helper function to resolve a localname or a username to a user ''' # raises DoesNotExist if user is now found try: - return models.User.viwer_aware_objects(viewer).get(localname=username) + return models.User.viewer_aware_objects(viewer).get(localname=username) except models.User.DoesNotExist: return models.User.viewer_aware_objects(viewer).get(username=username) From be9198fc4ff7def40d7cd4d5040e8170814a3a43 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 13:09:39 -0800 Subject: [PATCH 18/34] Another place where get reviews is called in tests --- bookwyrm/tests/models/test_user_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/tests/models/test_user_model.py b/bookwyrm/tests/models/test_user_model.py index 34f4c54d..84ff3e9b 100644 --- a/bookwyrm/tests/models/test_user_model.py +++ b/bookwyrm/tests/models/test_user_model.py @@ -12,7 +12,7 @@ class User(TestCase): def setUp(self): self.user = models.User.objects.create_user( 'mouse@%s' % DOMAIN, 'mouse@mouse.mouse', 'mouseword', - local=True, localname='mouse', name='hi') + local=True, localname='mouse', name='hi', bookwyrm_user=False) def test_computed_fields(self): ''' username instead of id here ''' From b9f06edc1b16a92af7bc02d526b83900e1a5c593 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 13:12:50 -0800 Subject: [PATCH 19/34] Fixes a few missed calls to get_user_from_username --- bookwyrm/views/feed.py | 2 +- bookwyrm/views/goal.py | 4 ++-- bookwyrm/views/list.py | 2 +- bookwyrm/views/rss_feed.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py index e67a893d..e7d179b9 100644 --- a/bookwyrm/views/feed.py +++ b/bookwyrm/views/feed.py @@ -91,7 +91,7 @@ class Status(View): def get(self, request, username, status_id): ''' display a particular status (and replies, etc) ''' try: - user = get_user_from_username(username) + user = get_user_from_username(request.user, username) status = models.Status.objects.select_subclasses().get( id=status_id, deleted=False) except ValueError: diff --git a/bookwyrm/views/goal.py b/bookwyrm/views/goal.py index 4f2d1b6f..97f13913 100644 --- a/bookwyrm/views/goal.py +++ b/bookwyrm/views/goal.py @@ -18,7 +18,7 @@ class Goal(View): ''' track books for the year ''' def get(self, request, username, year): ''' reading goal page ''' - user = get_user_from_username(username) + user = get_user_from_username(request.user, username) year = int(year) goal = models.AnnualGoal.objects.filter( year=year, user=user @@ -42,7 +42,7 @@ class Goal(View): def post(self, request, username, year): ''' update or create an annual goal ''' - user = get_user_from_username(username) + user = get_user_from_username(request.user, username) if user != request.user: return HttpResponseNotFound() diff --git a/bookwyrm/views/list.py b/bookwyrm/views/list.py index cfdf6d76..7286bfb4 100644 --- a/bookwyrm/views/list.py +++ b/bookwyrm/views/list.py @@ -65,7 +65,7 @@ class UserLists(View): page = int(request.GET.get('page', 1)) except ValueError: page = 1 - user = get_user_from_username(username) + user = get_user_from_username(request.user, username) lists = models.List.objects.filter(user=user).all() lists = privacy_filter( request.user, lists, ['public', 'followers', 'unlisted']) diff --git a/bookwyrm/views/rss_feed.py b/bookwyrm/views/rss_feed.py index 496689ff..aad227bf 100644 --- a/bookwyrm/views/rss_feed.py +++ b/bookwyrm/views/rss_feed.py @@ -11,7 +11,7 @@ class RssFeed(Feed): def get_object(self, request, username): ''' the user who's posts get serialized ''' - return get_user_from_username(username) + return get_user_from_username(request.user, username) def link(self, obj): From ffe5ce725145351c529923e77423f62f659e7610 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 13:23:41 -0800 Subject: [PATCH 20/34] User friendly-er add cover form bulma has failed me. Fixes #628 --- bookwyrm/templates/book.html | 24 ++++-------------------- bookwyrm/templates/edit_book.html | 2 +- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/bookwyrm/templates/book.html b/bookwyrm/templates/book.html index 0bef2856..bb1f2be7 100644 --- a/bookwyrm/templates/book.html +++ b/bookwyrm/templates/book.html @@ -42,26 +42,10 @@

    Add cover

    {% csrf_token %} -
    -
    -
    - -
    -
    -
    - -
    -
    + +
    {% endif %} diff --git a/bookwyrm/templates/edit_book.html b/bookwyrm/templates/edit_book.html index 6e7e434e..fb0bb81c 100644 --- a/bookwyrm/templates/edit_book.html +++ b/bookwyrm/templates/edit_book.html @@ -62,7 +62,7 @@

    Cover

    -

    {{ form.cover }}

    +

    {{ form.cover }}

    {% for error in form.cover.errors %}

    {{ error | escape }}

    {% endfor %} From 9ac332f6cc290284416eb892cefc24c897723174 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 13:26:44 -0800 Subject: [PATCH 21/34] Adds request user for rss test --- bookwyrm/tests/views/test_rss_feed.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bookwyrm/tests/views/test_rss_feed.py b/bookwyrm/tests/views/test_rss_feed.py index 2b5e415e..3d5bec49 100644 --- a/bookwyrm/tests/views/test_rss_feed.py +++ b/bookwyrm/tests/views/test_rss_feed.py @@ -41,6 +41,7 @@ class RssFeedView(TestCase): ''' load an rss feed ''' view = rss_feed.RssFeed() request = self.factory.get('/user/rss_user/rss') + request.user = self.user with patch("bookwyrm.models.SiteSettings.objects.get") as site: site.return_value = self.site result = view(request, username=self.user.username) From a0b57837a7bfa3b2e73aa9b8208800f64dfd7c53 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 13:34:16 -0800 Subject: [PATCH 22/34] Moves status templates into dir --- bookwyrm/templates/book.html | 2 +- bookwyrm/templates/feed/direct_messages.html | 2 +- bookwyrm/templates/feed/feed.html | 2 +- bookwyrm/templates/feed/thread.html | 2 +- bookwyrm/templates/snippets/{ => status}/status.html | 4 ++-- bookwyrm/templates/snippets/{ => status}/status_body.html | 6 +++--- .../templates/snippets/{ => status}/status_content.html | 0 bookwyrm/templates/snippets/{ => status}/status_header.html | 0 .../templates/snippets/{ => status}/status_options.html | 0 bookwyrm/templates/user/user.html | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) rename bookwyrm/templates/snippets/{ => status}/status.html (63%) rename bookwyrm/templates/snippets/{ => status}/status_body.html (89%) rename bookwyrm/templates/snippets/{ => status}/status_content.html (100%) rename bookwyrm/templates/snippets/{ => status}/status_header.html (100%) rename bookwyrm/templates/snippets/{ => status}/status_options.html (100%) diff --git a/bookwyrm/templates/book.html b/bookwyrm/templates/book.html index 0bef2856..4d15a3a5 100644 --- a/bookwyrm/templates/book.html +++ b/bookwyrm/templates/book.html @@ -242,7 +242,7 @@
    {% for review in reviews %}
    - {% include 'snippets/status.html' with status=review hide_book=True depth=1 %} + {% include 'snippets/status/status.html' with status=review hide_book=True depth=1 %}
    {% endfor %} diff --git a/bookwyrm/templates/feed/direct_messages.html b/bookwyrm/templates/feed/direct_messages.html index 8c53cbeb..f3102ee7 100644 --- a/bookwyrm/templates/feed/direct_messages.html +++ b/bookwyrm/templates/feed/direct_messages.html @@ -16,7 +16,7 @@ {% endif %} {% for activity in activities %}
    - {% include 'snippets/status.html' with status=activity %} + {% include 'snippets/status/status.html' with status=activity %}
    {% endfor %} diff --git a/bookwyrm/templates/feed/feed.html b/bookwyrm/templates/feed/feed.html index 8d6152e2..7029fd69 100644 --- a/bookwyrm/templates/feed/feed.html +++ b/bookwyrm/templates/feed/feed.html @@ -32,7 +32,7 @@ {% endif %} {% for activity in activities %}
    -{% include 'snippets/status.html' with status=activity %} +{% include 'snippets/status/status.html' with status=activity %}
    {% endfor %} diff --git a/bookwyrm/templates/feed/thread.html b/bookwyrm/templates/feed/thread.html index aa67d5bb..18ab6ea3 100644 --- a/bookwyrm/templates/feed/thread.html +++ b/bookwyrm/templates/feed/thread.html @@ -8,7 +8,7 @@ {% endwith %} {% endif %} -{% include 'snippets/status.html' with status=status main=is_root %} +{% include 'snippets/status/status.html' with status=status main=is_root %} {% if depth <= max_depth and direction >= 0 %} {% for reply in status|replies %} diff --git a/bookwyrm/templates/snippets/status.html b/bookwyrm/templates/snippets/status/status.html similarity index 63% rename from bookwyrm/templates/snippets/status.html rename to bookwyrm/templates/snippets/status/status.html index 13d5ee7c..162fad97 100644 --- a/bookwyrm/templates/snippets/status.html +++ b/bookwyrm/templates/snippets/status/status.html @@ -4,8 +4,8 @@ {% include 'snippets/avatar.html' with user=status.user %} {% include 'snippets/username.html' with user=status.user %} boosted - {% include 'snippets/status_body.html' with status=status|boosted_status %} + {% include 'snippets/status/status_body.html' with status=status|boosted_status %} {% else %} - {% include 'snippets/status_body.html' with status=status %} + {% include 'snippets/status/status_body.html' with status=status %} {% endif %} {% endif %} diff --git a/bookwyrm/templates/snippets/status_body.html b/bookwyrm/templates/snippets/status/status_body.html similarity index 89% rename from bookwyrm/templates/snippets/status_body.html rename to bookwyrm/templates/snippets/status/status_body.html index 00ae5460..dfc0ac68 100644 --- a/bookwyrm/templates/snippets/status_body.html +++ b/bookwyrm/templates/snippets/status/status_body.html @@ -5,13 +5,13 @@ {% block card-header %}

    - {% include 'snippets/status_header.html' with status=status %} + {% include 'snippets/status/status_header.html' with status=status %}

    {% endblock %} {% block card-content %} - {% include 'snippets/status_content.html' with status=status %} + {% include 'snippets/status/status_content.html' with status=status %} {% endblock %} @@ -55,7 +55,7 @@
    {{ status.published_date | post_date }}
    {% endblock %} diff --git a/bookwyrm/templates/snippets/status_content.html b/bookwyrm/templates/snippets/status/status_content.html similarity index 100% rename from bookwyrm/templates/snippets/status_content.html rename to bookwyrm/templates/snippets/status/status_content.html diff --git a/bookwyrm/templates/snippets/status_header.html b/bookwyrm/templates/snippets/status/status_header.html similarity index 100% rename from bookwyrm/templates/snippets/status_header.html rename to bookwyrm/templates/snippets/status/status_header.html diff --git a/bookwyrm/templates/snippets/status_options.html b/bookwyrm/templates/snippets/status/status_options.html similarity index 100% rename from bookwyrm/templates/snippets/status_options.html rename to bookwyrm/templates/snippets/status/status_options.html diff --git a/bookwyrm/templates/user/user.html b/bookwyrm/templates/user/user.html index c7f6d3b5..e1c19e53 100644 --- a/bookwyrm/templates/user/user.html +++ b/bookwyrm/templates/user/user.html @@ -64,7 +64,7 @@
    {% for activity in activities %}
    - {% include 'snippets/status.html' with status=activity %} + {% include 'snippets/status/status.html' with status=activity %}
    {% endfor %} {% if not activities %} From f9dd0b0246032b769837c1244b8f88ef5467e2c3 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 13:42:20 -0800 Subject: [PATCH 23/34] Groups in book preview only used by status templates --- bookwyrm/templates/snippets/{ => status}/book_preview.html | 0 bookwyrm/templates/snippets/status/status_content.html | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename bookwyrm/templates/snippets/{ => status}/book_preview.html (100%) diff --git a/bookwyrm/templates/snippets/book_preview.html b/bookwyrm/templates/snippets/status/book_preview.html similarity index 100% rename from bookwyrm/templates/snippets/book_preview.html rename to bookwyrm/templates/snippets/status/book_preview.html diff --git a/bookwyrm/templates/snippets/status/status_content.html b/bookwyrm/templates/snippets/status/status_content.html index d6dd5ef5..0f59f7fc 100644 --- a/bookwyrm/templates/snippets/status/status_content.html +++ b/bookwyrm/templates/snippets/status/status_content.html @@ -54,9 +54,9 @@ {% if status.book or status.mention_books.count %}
    {% if status.book %} - {% include 'snippets/book_preview.html' with book=status.book %} + {% include 'snippets/status/book_preview.html' with book=status.book %} {% elif status.mention_books.count %} - {% include 'snippets/book_preview.html' with book=status.mention_books.first %} + {% include 'snippets/status/book_preview.html' with book=status.mention_books.first %} {% endif %}
    {% endif %} From d0c46060e82282c9a3f1008a4e6e003c64185026 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 14:00:19 -0800 Subject: [PATCH 24/34] Adds stars to book preview in status --- bookwyrm/templates/snippets/stars.html | 2 +- bookwyrm/templates/snippets/status/book_preview.html | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bookwyrm/templates/snippets/stars.html b/bookwyrm/templates/snippets/stars.html index 7d5b63d7..d1576807 100644 --- a/bookwyrm/templates/snippets/stars.html +++ b/bookwyrm/templates/snippets/stars.html @@ -1,7 +1,7 @@

    {% if rating %}{{ rating|floatformat }} star{{ rating|floatformat | pluralize }}{% else %}No rating{% endif %} {% for i in '12345'|make_list %} -

    diff --git a/bookwyrm/templates/snippets/status/book_preview.html b/bookwyrm/templates/snippets/status/book_preview.html index 0c75f9b1..920b9f53 100644 --- a/bookwyrm/templates/snippets/status/book_preview.html +++ b/bookwyrm/templates/snippets/status/book_preview.html @@ -3,6 +3,7 @@
    {% include 'snippets/book_cover.html' with book=book %} + {% include 'snippets/stars.html' with rating=book|rating:request.user %} {% include 'snippets/shelve_button/shelve_button.html' with book=book %}
    From 31c9c07e384e4aa0a14cd73282d2bc760f94eb28 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 14:06:08 -0800 Subject: [PATCH 25/34] Makes covers clickable on discover page --- bookwyrm/templates/{ => discover}/discover.html | 12 ++++++------ .../{snippets => }/discover/large-book.html | 2 +- .../{snippets => }/discover/small-book.html | 2 +- bookwyrm/views/landing.py | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) rename bookwyrm/templates/{ => discover}/discover.html (85%) rename bookwyrm/templates/{snippets => }/discover/large-book.html (85%) rename bookwyrm/templates/{snippets => }/discover/small-book.html (79%) diff --git a/bookwyrm/templates/discover.html b/bookwyrm/templates/discover/discover.html similarity index 85% rename from bookwyrm/templates/discover.html rename to bookwyrm/templates/discover/discover.html index 27e26e53..0b6f8a1f 100644 --- a/bookwyrm/templates/discover.html +++ b/bookwyrm/templates/discover/discover.html @@ -63,18 +63,18 @@
    - {% include 'snippets/discover/large-book.html' with book=books.0 %} + {% include 'discover/large-book.html' with book=books.0 %}
    - {% include 'snippets/discover/small-book.html' with book=books.1 %} + {% include 'discover/small-book.html' with book=books.1 %}
    - {% include 'snippets/discover/small-book.html' with book=books.2 %} + {% include 'discover/small-book.html' with book=books.2 %}
    @@ -83,18 +83,18 @@
    - {% include 'snippets/discover/small-book.html' with book=books.3 %} + {% include 'discover/small-book.html' with book=books.3 %}
    - {% include 'snippets/discover/small-book.html' with book=books.4 %} + {% include 'discover/small-book.html' with book=books.4 %}
    - {% include 'snippets/discover/large-book.html' with book=books.5 %} + {% include 'discover/large-book.html' with book=books.5 %}
    diff --git a/bookwyrm/templates/snippets/discover/large-book.html b/bookwyrm/templates/discover/large-book.html similarity index 85% rename from bookwyrm/templates/snippets/discover/large-book.html rename to bookwyrm/templates/discover/large-book.html index 37b35947..401411a1 100644 --- a/bookwyrm/templates/snippets/discover/large-book.html +++ b/bookwyrm/templates/discover/large-book.html @@ -2,7 +2,7 @@ {% if book %}
    - {% include 'snippets/book_cover.html' with book=book size="large" %} + {% include 'snippets/book_cover.html' with book=book size="large" %} {% include 'snippets/stars.html' with rating=ratings|dict_key:book.id %}
    diff --git a/bookwyrm/templates/snippets/discover/small-book.html b/bookwyrm/templates/discover/small-book.html similarity index 79% rename from bookwyrm/templates/snippets/discover/small-book.html rename to bookwyrm/templates/discover/small-book.html index be399df6..d1676b6b 100644 --- a/bookwyrm/templates/snippets/discover/small-book.html +++ b/bookwyrm/templates/discover/small-book.html @@ -1,6 +1,6 @@ {% load bookwyrm_tags %} {% if book %} -{% include 'snippets/book_cover.html' with book=book %} +{% include 'snippets/book_cover.html' with book=book %} {% if ratings %} {% include 'snippets/stars.html' with rating=ratings|dict_key:book.id %} {% endif %} diff --git a/bookwyrm/views/landing.py b/bookwyrm/views/landing.py index 0d841ef0..854f3a9b 100644 --- a/bookwyrm/views/landing.py +++ b/bookwyrm/views/landing.py @@ -56,4 +56,4 @@ class Discover(View): 'books': list(set(books)), 'ratings': ratings } - return TemplateResponse(request, 'discover.html', data) + return TemplateResponse(request, 'discover/discover.html', data) From 1eaff91513e7f3ffa156c2f108d07e06d509b545 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 14:08:52 -0800 Subject: [PATCH 26/34] Makes the interactive rating element a different color --- bookwyrm/templates/snippets/rate_action.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/templates/snippets/rate_action.html b/bookwyrm/templates/snippets/rate_action.html index 833f2b88..cc94f675 100644 --- a/bookwyrm/templates/snippets/rate_action.html +++ b/bookwyrm/templates/snippets/rate_action.html @@ -9,7 +9,7 @@ -
    +
    {% for i in '12345'|make_list %} From 3de8a20d39db8ab2036b0352af435090ff90c712 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 14:36:24 -0800 Subject: [PATCH 27/34] Fixes boolean for is bookwyrm user in test --- bookwyrm/tests/models/test_user_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/tests/models/test_user_model.py b/bookwyrm/tests/models/test_user_model.py index 84ff3e9b..ed3ad41a 100644 --- a/bookwyrm/tests/models/test_user_model.py +++ b/bookwyrm/tests/models/test_user_model.py @@ -64,7 +64,7 @@ class User(TestCase): self.assertEqual(activity['name'], self.user.name) self.assertEqual(activity['inbox'], self.user.inbox) self.assertEqual(activity['outbox'], self.user.outbox) - self.assertEqual(activity['bookwyrmUser'], True) + self.assertEqual(activity['bookwyrmUser'], False) self.assertEqual(activity['discoverable'], True) self.assertEqual(activity['type'], 'Person') From d3381d7a799d98f40a59c962be5b124d0cc6ecc3 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 14:41:33 -0800 Subject: [PATCH 28/34] Paginates invite page I sure have sent out a lot of invites --- bookwyrm/templates/settings/manage_invites.html | 1 + bookwyrm/views/invite.py | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/bookwyrm/templates/settings/manage_invites.html b/bookwyrm/templates/settings/manage_invites.html index 03b68b20..42668a3d 100644 --- a/bookwyrm/templates/settings/manage_invites.html +++ b/bookwyrm/templates/settings/manage_invites.html @@ -22,6 +22,7 @@ {% endfor %} + {% include 'snippets/pagination.html' with page=invites path=request.path %}
    diff --git a/bookwyrm/views/invite.py b/bookwyrm/views/invite.py index bd0715a0..ed7b1f5f 100644 --- a/bookwyrm/views/invite.py +++ b/bookwyrm/views/invite.py @@ -1,5 +1,6 @@ ''' invites when registration is closed ''' from django.contrib.auth.decorators import login_required, permission_required +from django.core.paginator import Paginator from django.http import HttpResponseBadRequest from django.shortcuts import get_object_or_404, redirect from django.template.response import TemplateResponse @@ -7,6 +8,7 @@ from django.utils.decorators import method_decorator from django.views import View from bookwyrm import forms, models +from bookwyrm.settings import PAGE_LENGTH # pylint: disable= no-self-use @@ -18,10 +20,18 @@ class ManageInvites(View): ''' create invites ''' def get(self, request): ''' invite management page ''' + try: + page = int(request.GET.get('page', 1)) + except ValueError: + page = 1 + + paginated = Paginator(models.SiteInvite.objects.filter( + user=request.user + ).order_by('-created_date'), PAGE_LENGTH) + data = { 'title': 'Invitations', - 'invites': models.SiteInvite.objects.filter( - user=request.user).order_by('-created_date'), + 'invites': paginated.page(page), 'form': forms.CreateInviteForm(), } return TemplateResponse(request, 'settings/manage_invites.html', data) From baed291889eb3672c6c503b7b023e28a72ca26ce Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 14:45:39 -0800 Subject: [PATCH 29/34] Don't broadcast after saving remote server --- bookwyrm/models/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index a1f927b2..094a13d5 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -319,7 +319,7 @@ def set_remote_server(user_id): actor_parts = urlparse(user.remote_id) user.federated_server = \ get_or_create_remote_server(actor_parts.netloc) - user.save() + user.save(broadcast=False) if user.bookwyrm_user: get_remote_reviews.delay(user.outbox) From 384187a263c649c4b563b1d422dc09e5c0f07ede Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 15:21:37 -0800 Subject: [PATCH 30/34] Moves create invite form to top of invite page --- .../templates/settings/manage_invites.html | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/bookwyrm/templates/settings/manage_invites.html b/bookwyrm/templates/settings/manage_invites.html index 42668a3d..086615a9 100644 --- a/bookwyrm/templates/settings/manage_invites.html +++ b/bookwyrm/templates/settings/manage_invites.html @@ -2,29 +2,6 @@ {% block header %}Invites{% endblock %} {% load humanize %} {% block panel %} -
    - - - - - - - - {% if not invites %} - - {% endif %} - {% for invite in invites %} - - - - - - - {% endfor %} -
    LinkExpiresMax usesTimes used
    No active invites
    {{ invite.link }}{{ invite.expiry|naturaltime }}{{ invite.use_limit }}{{ invite.times_used }}
    - {% include 'snippets/pagination.html' with page=invites path=request.path %} -
    -

    Generate New Invite

    @@ -48,4 +25,27 @@
    + +
    + + + + + + + + {% if not invites %} + + {% endif %} + {% for invite in invites %} + + + + + + + {% endfor %} +
    LinkExpiresMax usesTimes used
    No active invites
    {{ invite.link }}{{ invite.expiry|naturaltime }}{{ invite.use_limit }}{{ invite.times_used }}
    + {% include 'snippets/pagination.html' with page=invites path=request.path %} +
    {% endblock %} From 23fb5f62a2e58d4869b1ab79aa72f105dda305f7 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 15:25:26 -0800 Subject: [PATCH 31/34] Keep invite settings in form after save --- bookwyrm/views/invite.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bookwyrm/views/invite.py b/bookwyrm/views/invite.py index ed7b1f5f..6b3611fc 100644 --- a/bookwyrm/views/invite.py +++ b/bookwyrm/views/invite.py @@ -46,7 +46,15 @@ class ManageInvites(View): invite.user = request.user invite.save() - return redirect('/settings/invites') + paginated = Paginator(models.SiteInvite.objects.filter( + user=request.user + ).order_by('-created_date'), PAGE_LENGTH) + data = { + 'title': 'Invitations', + 'invites': paginated.page(1), + 'form': form + } + return TemplateResponse(request, 'settings/manage_invites.html', data) class Invite(View): From 744de313c8ab2febf33a060a343726365b583467 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 23 Feb 2021 17:23:11 -0800 Subject: [PATCH 32/34] Makes comment and fav/boost buttons the same color when selected --- bookwyrm/templates/snippets/boost_button.html | 2 +- bookwyrm/templates/snippets/fav_button.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/templates/snippets/boost_button.html b/bookwyrm/templates/snippets/boost_button.html index 08daae64..bf914379 100644 --- a/bookwyrm/templates/snippets/boost_button.html +++ b/bookwyrm/templates/snippets/boost_button.html @@ -11,7 +11,7 @@
    {% csrf_token %} -