From 0a8d080b628874916db68024911c39ebf8cc6369 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 31 Mar 2020 18:58:13 -0700 Subject: [PATCH 01/14] Fixes for goodreads import + ol changes --- fedireads/connectors/openlibrary.py | 7 ++++--- fedireads/outgoing.py | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/fedireads/connectors/openlibrary.py b/fedireads/connectors/openlibrary.py index 842d31d4..53be173d 100644 --- a/fedireads/connectors/openlibrary.py +++ b/fedireads/connectors/openlibrary.py @@ -167,7 +167,7 @@ class Connector(AbstractConnector): } author = update_from_mappings(author, data, mappings) # TODO this is making some BOLD assumption - name = data['name'] + name = data.get('name') author.last_name = name.split(' ')[-1] author.first_name = ' '.join(name.split(' ')[:-1]) author.save() @@ -204,8 +204,9 @@ def set_default_edition(work): options, key=lambda e: e.published_date.year if e.published_date else None ) - options[0].default = True - options[0].save() + if len(options): + options[0].default = True + options[0].save() def get_description(description_blob): diff --git a/fedireads/outgoing.py b/fedireads/outgoing.py index dc60b2c0..48019ffb 100644 --- a/fedireads/outgoing.py +++ b/fedireads/outgoing.py @@ -167,6 +167,10 @@ def handle_import_books(user, items): identifier=item.shelf, user=user ) + if isinstance(item.book, models.Work): + item.book = item.book.default_edition + if not item.book: + continue _, created = models.ShelfBook.objects.get_or_create( book=item.book, shelf=desired_shelf, added_by=user) if created: From d88ed7a90c22628f7589cd9baa12c6aac3f525b6 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 31 Mar 2020 20:06:42 -0700 Subject: [PATCH 02/14] this isn't mastodon --- fedireads/wellknown.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fedireads/wellknown.py b/fedireads/wellknown.py index 66c454f8..6e4198fa 100644 --- a/fedireads/wellknown.py +++ b/fedireads/wellknown.py @@ -55,7 +55,7 @@ def nodeinfo(request): return JsonResponse({ "version": "2.0", "software": { - "name": "mastodon", + "name": "fedireads", "version": "0.0.1" }, "protocols": [ From 87f12a970d54f9d44a609de8bc8ee74e37ad910f Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 1 Apr 2020 11:22:22 -0700 Subject: [PATCH 03/14] Convert comments to AP Notes and unrated reviews Works on #121 --- fedireads/activitypub/status.py | 25 +++++++++++-------- fedireads/forms.py | 6 +---- .../migrations/0028_auto_20200401_1824.py | 23 +++++++++++++++++ fedireads/models/status.py | 9 ++++--- fedireads/outgoing.py | 4 +-- fedireads/status.py | 12 +++++---- fedireads/view_actions.py | 5 ++-- 7 files changed, 54 insertions(+), 30 deletions(-) create mode 100644 fedireads/migrations/0028_auto_20200401_1824.py diff --git a/fedireads/activitypub/status.py b/fedireads/activitypub/status.py index 779bb3af..13f6e400 100644 --- a/fedireads/activitypub/status.py +++ b/fedireads/activitypub/status.py @@ -19,18 +19,24 @@ def get_comment(comment): status = get_status(comment) status['inReplyToBook'] = comment.book.absolute_id status['fedireadsType'] = comment.status_type - status['name'] = comment.name return status def get_review_article(review): ''' a book review formatted for a non-fedireads isntance (mastodon) ''' status = get_status(review) - name = 'Review of "%s" (%d stars): %s' % ( - review.book.title, - review.rating, - review.name - ) + if review.rating: + name = 'Review of "%s" (%d stars): %s' % ( + review.book.title, + review.rating, + review.name + ) + else: + name = 'Review of "%s": %s' % ( + review.book.title, + review.name + ) + status['name'] = name return status @@ -38,11 +44,8 @@ def get_review_article(review): def get_comment_article(comment): ''' a book comment formatted for a non-fedireads isntance (mastodon) ''' status = get_status(comment) - name = '%s (comment on "%s")' % ( - comment.name, - comment.book.title - ) - status['name'] = name + status['content'] += '

(comment on "%s")' % \ + (comment.book.absolute_id, comment.book.title) return status diff --git a/fedireads/forms.py b/fedireads/forms.py index 87dc5dfd..fccfe74f 100644 --- a/fedireads/forms.py +++ b/fedireads/forms.py @@ -31,9 +31,6 @@ class ReviewForm(ModelForm): model = models.Review fields = ['name', 'rating', 'content'] help_texts = {f: None for f in fields} - content = IntegerField(validators=[ - MinValueValidator(0), MaxValueValidator(5) - ]) labels = { 'name': 'Title', 'rating': 'Rating (out of 5)', @@ -44,10 +41,9 @@ class ReviewForm(ModelForm): class CommentForm(ModelForm): class Meta: model = models.Comment - fields = ['name', 'content'] + fields = ['content'] help_texts = {f: None for f in fields} labels = { - 'name': 'Title', 'content': 'Comment', } diff --git a/fedireads/migrations/0028_auto_20200401_1824.py b/fedireads/migrations/0028_auto_20200401_1824.py new file mode 100644 index 00000000..a97f0006 --- /dev/null +++ b/fedireads/migrations/0028_auto_20200401_1824.py @@ -0,0 +1,23 @@ +# Generated by Django 3.0.3 on 2020-04-01 18:24 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('fedireads', '0027_auto_20200330_2232'), + ] + + operations = [ + migrations.RemoveField( + model_name='comment', + name='name', + ), + migrations.AlterField( + model_name='review', + name='rating', + field=models.IntegerField(blank=True, default=None, null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(5)]), + ), + ] diff --git a/fedireads/models/status.py b/fedireads/models/status.py index f8cd1921..52068266 100644 --- a/fedireads/models/status.py +++ b/fedireads/models/status.py @@ -48,12 +48,11 @@ class Status(FedireadsModel): class Comment(Status): ''' like a review but without a rating and transient ''' - name = models.CharField(max_length=255) book = models.ForeignKey('Edition', on_delete=models.PROTECT) def save(self, *args, **kwargs): self.status_type = 'Comment' - self.activity_type = 'Article' + self.activity_type = 'Note' super().save(*args, **kwargs) @@ -62,8 +61,10 @@ class Review(Status): name = models.CharField(max_length=255) book = models.ForeignKey('Edition', on_delete=models.PROTECT) rating = models.IntegerField( - default=0, - validators=[MinValueValidator(0), MaxValueValidator(5)] + default=None, + null=True, + blank=True, + validators=[MinValueValidator(1), MaxValueValidator(5)] ) def save(self, *args, **kwargs): diff --git a/fedireads/outgoing.py b/fedireads/outgoing.py index 48019ffb..e0687048 100644 --- a/fedireads/outgoing.py +++ b/fedireads/outgoing.py @@ -208,10 +208,10 @@ def handle_review(user, book, name, content, rating): broadcast(user, article_create_activity, other_recipients) -def handle_comment(user, book, name, content): +def handle_comment(user, book, content): ''' post a review ''' # validated and saves the review in the database so it has an id - comment = create_comment(user, book, name, content) + comment = create_comment(user, book, content) comment_activity = activitypub.get_comment(comment) comment_create_activity = activitypub.get_create(user, comment_activity) diff --git a/fedireads/status.py b/fedireads/status.py index bc79b99c..c0504a64 100644 --- a/fedireads/status.py +++ b/fedireads/status.py @@ -31,7 +31,11 @@ def create_review(user, possible_book, name, content, rating): content = sanitize(content) # no ratings outside of 0-5 - rating = rating if 0 <= rating <= 5 else 0 + try: + rating = int(rating) + rating = rating if 1 <= rating <= 5 else None + except ValueError: + rating = None return models.Review.objects.create( user=user, @@ -46,19 +50,18 @@ def create_comment_from_activity(author, activity): ''' parse an activity json blob into a status ''' book = activity['inReplyToBook'] book = book.split('/')[-1] - name = activity.get('name') content = activity.get('content') published = activity.get('published') remote_id = activity['id'] - comment = create_comment(author, book, name, content) + comment = create_comment(author, book, content) comment.published_date = published comment.remote_id = remote_id comment.save() return comment -def create_comment(user, possible_book, name, content): +def create_comment(user, possible_book, content): ''' a book comment has been added ''' # throws a value error if the book is not found book = get_or_create_book(possible_book) @@ -67,7 +70,6 @@ def create_comment(user, possible_book, name, content): return models.Comment.objects.create( user=user, book=book, - name=name, content=content, ) diff --git a/fedireads/view_actions.py b/fedireads/view_actions.py index f9a2544b..62a25f03 100644 --- a/fedireads/view_actions.py +++ b/fedireads/view_actions.py @@ -170,7 +170,7 @@ def review(request): # TODO: validation, htmlification name = form.data.get('name') content = form.data.get('content') - rating = int(form.data.get('rating')) + rating = form.data.get('rating') outgoing.handle_review(request.user, book_identifier, name, content, rating) return redirect('/book/%s' % book_identifier) @@ -186,10 +186,9 @@ def comment(request): return redirect('/book/%s' % book_identifier) # TODO: validation, htmlification - name = form.data.get('name') content = form.data.get('content') - outgoing.handle_comment(request.user, book_identifier, name, content) + outgoing.handle_comment(request.user, book_identifier, content) return redirect('/book/%s' % book_identifier) From c85eba53dbdc3bd0e76f3258b454c8ba466ba228 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 1 Apr 2020 12:32:24 -0700 Subject: [PATCH 04/14] Make user page covers shelf clickable --- fedireads/static/js/feed.js | 11 ----- fedireads/static/js/shared.js | 11 +++++ fedireads/templates/feed.html | 17 +------ .../templates/snippets/covers_shelf.html | 46 +++++++++++++------ fedireads/templates/user.html | 4 +- 5 files changed, 44 insertions(+), 45 deletions(-) delete mode 100644 fedireads/static/js/feed.js diff --git a/fedireads/static/js/feed.js b/fedireads/static/js/feed.js deleted file mode 100644 index dd6b186f..00000000 --- a/fedireads/static/js/feed.js +++ /dev/null @@ -1,11 +0,0 @@ -function show_compose(element) { - var visible_compose_boxes = document.getElementsByClassName('visible'); - for (var i = 0; i < visible_compose_boxes.length; i++) { - visible_compose_boxes[i].className = 'compose-suggestion'; - } - - var target_id = 'compose-' + element.id; - var target = document.getElementById(target_id); - target.className += ' visible'; -} - diff --git a/fedireads/static/js/shared.js b/fedireads/static/js/shared.js index 87c3a123..c63e44ca 100644 --- a/fedireads/static/js/shared.js +++ b/fedireads/static/js/shared.js @@ -1,3 +1,14 @@ +function show_compose(element) { + var visible_compose_boxes = document.getElementsByClassName('visible'); + for (var i = 0; i < visible_compose_boxes.length; i++) { + visible_compose_boxes[i].className = 'compose-suggestion'; + } + + var target_id = 'compose-' + element.id; + var target = document.getElementById(target_id); + target.className += ' visible'; +} + function hide_element(element) { var classes = element.parentElement.className; element.parentElement.className = classes.replace('visible', ''); diff --git a/fedireads/templates/feed.html b/fedireads/templates/feed.html index 87e7fc2a..f77865ea 100644 --- a/fedireads/templates/feed.html +++ b/fedireads/templates/feed.html @@ -2,22 +2,8 @@ {% load fr_display %} {% block content %} -
- {% include 'snippets/covers_shelf.html' with shelves=shelves user=request.user %} -
+{% include 'snippets/covers_shelf.html' with shelves=shelves user=request.user %} -{% for shelf in shelves %} - {% for book in shelf.books %} -
- - Close - -
- {% include 'snippets/create_status.html' with book=book user=request.user %} -
-
- {% endfor %} -{% endfor %}
@@ -31,5 +17,4 @@ {% endfor %}
- {% endblock %} diff --git a/fedireads/templates/snippets/covers_shelf.html b/fedireads/templates/snippets/covers_shelf.html index 74c59341..feddbfa7 100644 --- a/fedireads/templates/snippets/covers_shelf.html +++ b/fedireads/templates/snippets/covers_shelf.html @@ -1,20 +1,36 @@ {% load fr_display %} -{% for shelf in shelves %} -{% if shelf.books %} -
-

{{ shelf.name }} - {% if shelf.size > shelf.books|length %} - (See all {{ shelf.size }}) - {% endif %} -

-
- {% for book in shelf.books %} -
- {% include 'snippets/book_cover.html' with book=book %} - {% include 'snippets/shelve_button.html' with book=book %} + +
+ {% for shelf in shelves %} + {% if shelf.books %} +
+

{{ shelf.name }} + {% if shelf.size > shelf.books|length %} + (See all {{ shelf.size }}) + {% endif %} +

+
+ {% for book in shelf.books %} +
+ {% include 'snippets/book_cover.html' with book=book %} + {% include 'snippets/shelve_button.html' with book=book %} +
+ {% endfor %}
- {% endfor %} +
+ {% endif %} + {% endfor %} +
+ +{% for shelf in shelves %} + {% for book in shelf.books %} +
+ + Close + +
+ {% include 'snippets/create_status.html' with book=book user=request.user %}
-{% endif %} + {% endfor %} {% endfor %} diff --git a/fedireads/templates/user.html b/fedireads/templates/user.html index ca67750a..bfb16fc7 100644 --- a/fedireads/templates/user.html +++ b/fedireads/templates/user.html @@ -2,9 +2,7 @@ {% block content %} {% include 'user_header.html' with user=user %} -
- {% include 'snippets/covers_shelf.html' with shelves=shelves user=user %} -
+{% include 'snippets/covers_shelf.html' with shelves=shelves user=user %}

User Activity

From 263b90599cece9677cf2c024d16455d392613b61 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 1 Apr 2020 14:04:59 -0700 Subject: [PATCH 05/14] Click through to book pages for no javascript --- fedireads/static/js/shared.js | 3 ++- fedireads/templates/snippets/covers_shelf.html | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/fedireads/static/js/shared.js b/fedireads/static/js/shared.js index c63e44ca..1859c4af 100644 --- a/fedireads/static/js/shared.js +++ b/fedireads/static/js/shared.js @@ -1,4 +1,5 @@ -function show_compose(element) { +function show_compose(element, e) { + e.preventDefault(); var visible_compose_boxes = document.getElementsByClassName('visible'); for (var i = 0; i < visible_compose_boxes.length; i++) { visible_compose_boxes[i].className = 'compose-suggestion'; diff --git a/fedireads/templates/snippets/covers_shelf.html b/fedireads/templates/snippets/covers_shelf.html index feddbfa7..d6a8b880 100644 --- a/fedireads/templates/snippets/covers_shelf.html +++ b/fedireads/templates/snippets/covers_shelf.html @@ -11,8 +11,8 @@
{% for book in shelf.books %} -
- {% include 'snippets/book_cover.html' with book=book %} +
+ {% include 'snippets/book_cover.html' with book=book %} {% include 'snippets/shelve_button.html' with book=book %}
{% endfor %} From 772ba1840b0fd6be5b67d4531e0bf3f77b14e33c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 1 Apr 2020 14:18:46 -0700 Subject: [PATCH 06/14] Don't break on absent author name --- fedireads/connectors/openlibrary.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fedireads/connectors/openlibrary.py b/fedireads/connectors/openlibrary.py index 53be173d..dd68920d 100644 --- a/fedireads/connectors/openlibrary.py +++ b/fedireads/connectors/openlibrary.py @@ -168,8 +168,9 @@ class Connector(AbstractConnector): author = update_from_mappings(author, data, mappings) # TODO this is making some BOLD assumption name = data.get('name') - author.last_name = name.split(' ')[-1] - author.first_name = ' '.join(name.split(' ')[:-1]) + if name: + author.last_name = name.split(' ')[-1] + author.first_name = ' '.join(name.split(' ')[:-1]) author.save() return author From 8d7ded1c691657d432dc26c87c3ca22cfb02a242 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 1 Apr 2020 14:32:56 -0700 Subject: [PATCH 07/14] display import books in a row --- fedireads/static/format.css | 4 +++ fedireads/templates/snippets/status.html | 32 ++--------------- .../templates/snippets/status_content.html | 36 +++++++++++++++++++ 3 files changed, 42 insertions(+), 30 deletions(-) create mode 100644 fedireads/templates/snippets/status_content.html diff --git a/fedireads/static/format.css b/fedireads/static/format.css index f0b5e1a8..28dc98ac 100644 --- a/fedireads/static/format.css +++ b/fedireads/static/format.css @@ -607,3 +607,7 @@ a .icon { position: absolute; overflow: hidden; } + +.book-row .book-cover { + float: none; +} diff --git a/fedireads/templates/snippets/status.html b/fedireads/templates/snippets/status.html index 92900b27..89f59fe3 100644 --- a/fedireads/templates/snippets/status.html +++ b/fedireads/templates/snippets/status.html @@ -22,36 +22,8 @@ - {% if not hide_book and status.mention_books.count %} - {% for book in status.mention_books.all|slice:"0:3" %} -
- {% if status.status_type == 'Review' %} - {% include 'snippets/book.html' with book=book %} - {% else %} - {% include 'snippets/book.html' with book=book description=True %} - {% endif %} -
- {% endfor %} - {% endif %} - {% if not hide_book and status.book%} -
- {% if status.status_type == 'Review' %} - {% include 'snippets/book.html' with book=status.book %} - {% else %} - {% include 'snippets/book.html' with book=status.book description=True %} - {% endif %} -
- {% endif %} - {% if status.status_type == 'Review' %}

{{ status.name }} - {{ status.rating | stars }} stars, by {% include 'snippets/username.html' with user=status.user %} -

{% endif %} - {% if status.status_type != 'Update' and status.status_type != 'Boost' %} -
{{ status.content | safe }}
- {% endif %} - {% if status.status_type == 'Boost' %} - {% include 'snippets/status.html' with status=status.boosted_status depth=depth|add:1 %} - {% endif %} - {% if not max_depth and status.reply_parent or status|replies %}

Thread{% endif %} + {% include 'snippets/status_content.html' with status=status %} +

{% if status.status_type != 'Boost' %} {% include 'snippets/interaction.html' with activity=status %} diff --git a/fedireads/templates/snippets/status_content.html b/fedireads/templates/snippets/status_content.html new file mode 100644 index 00000000..7da75784 --- /dev/null +++ b/fedireads/templates/snippets/status_content.html @@ -0,0 +1,36 @@ +{% load fr_display %} + +{% if not hide_book and status.mention_books.count %} +
+{% for book in status.mention_books.all|slice:"0:3" %} +
+ {% if status.status_type == 'Review' %} + {% include 'snippets/book.html' with book=book %} + {% else %} + {% include 'snippets/book.html' with book=book description=True %} + {% endif %} +
+{% endfor %} +
+{% endif %} + +{% if not hide_book and status.book%} +
+ {% if status.status_type == 'Review' %} + {% include 'snippets/book.html' with book=status.book %} + {% else %} + {% include 'snippets/book.html' with book=status.book description=True %} + {% endif %} +
+{% endif %} +{% if status.status_type == 'Review' %}

{{ status.name }} + {{ status.rating | stars }} stars, by {% include 'snippets/username.html' with user=status.user %} +

{% endif %} +{% if status.status_type != 'Update' and status.status_type != 'Boost' %} +
{{ status.content | safe }}
+{% endif %} +{% if status.status_type == 'Boost' %} + {% include 'snippets/status.html' with status=status.boosted_status %} +{% endif %} +{% if not max_depth and status.reply_parent or status|replies %}

Thread{% endif %} + From 29693127bad44690b0f9bf090d3cabf4ea0aa147 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 1 Apr 2020 14:55:32 -0700 Subject: [PATCH 08/14] Fixes boost display --- fedireads/models/status.py | 1 + fedireads/static/format.css | 12 +++++-- fedireads/templates/snippets/status.html | 33 +++++++------------ .../templates/snippets/status_content.html | 2 +- .../templates/snippets/status_header.html | 21 ++++++++++++ fedireads/templatetags/fr_display.py | 7 ++++ 6 files changed, 52 insertions(+), 24 deletions(-) create mode 100644 fedireads/templates/snippets/status_header.html diff --git a/fedireads/models/status.py b/fedireads/models/status.py index 52068266..91ec0ab5 100644 --- a/fedireads/models/status.py +++ b/fedireads/models/status.py @@ -101,6 +101,7 @@ class Boost(Status): self.status_type = 'Boost' self.activity_type = 'Announce' super().save(*args, **kwargs) + # This constraint can't work as it would cross tables. # class Meta: # unique_together = ('user', 'boosted_status') diff --git a/fedireads/static/format.css b/fedireads/static/format.css index 28dc98ac..9f63ab7f 100644 --- a/fedireads/static/format.css +++ b/fedireads/static/format.css @@ -300,8 +300,8 @@ button.warning { } .user-pic { - width: 2rem; - height: 2rem; + width: 2em; + height: 2em; border-radius: 50%; vertical-align: top; position: relative; @@ -577,6 +577,14 @@ th, td { .post .user-pic, .compose-suggestion .user-pic { right: 0.25em; } +.post h2 .subhead { + display: block; + margin-left: 2em; +} +.post .subhead .time-ago { + display: none; +} + .comment-thread .post { margin-left: 4em; diff --git a/fedireads/templates/snippets/status.html b/fedireads/templates/snippets/status.html index 89f59fe3..2ddcbe29 100644 --- a/fedireads/templates/snippets/status.html +++ b/fedireads/templates/snippets/status.html @@ -1,31 +1,22 @@ -{% load humanize %} {% load fr_display %} -

+ +
+

- {% include 'snippets/avatar.html' with user=status.user %} - {% include 'snippets/username.html' with user=status.user %} - {% if status.status_type == 'Update' %} - {{ status.content | safe }} - {% elif status.status_type == 'Review' %} - reviewed {{ status.book.title }} - {% elif status.status_type == 'Comment' %} - commented on {{ status.book.title }} - {% elif status.status_type == 'Boost' %} - boosted - {% elif status.reply_parent %} - {% with parent_status=status|parent %} - replied to {% include 'snippets/username.html' with user=parent_status.user possessive=True %} {{ parent_status.status_type|lower }} - {% endwith %} - {% endif %} - - {{ status.published_date | naturaltime }} - + {% if status.boosted_status %} + {% include 'snippets/status_header.html' with status=status.boosted_status %} + {% include 'snippets/status_header.html' with status=status %} + {% else %} + {% include 'snippets/status_header.html' with status=status %} + {% endif %}

{% include 'snippets/status_content.html' with status=status %}
-{% if status.status_type != 'Boost' %} +{% if status.status_type == 'Boost' %} +{% include 'snippets/interaction.html' with activity=status|boosted_status %} +{% else %} {% include 'snippets/interaction.html' with activity=status %} {% endif %} diff --git a/fedireads/templates/snippets/status_content.html b/fedireads/templates/snippets/status_content.html index 7da75784..7029ce9d 100644 --- a/fedireads/templates/snippets/status_content.html +++ b/fedireads/templates/snippets/status_content.html @@ -30,7 +30,7 @@
{{ status.content | safe }}
{% endif %} {% if status.status_type == 'Boost' %} - {% include 'snippets/status.html' with status=status.boosted_status %} + {% include 'snippets/status_content.html' with status=status|boosted_status %} {% endif %} {% if not max_depth and status.reply_parent or status|replies %}

Thread{% endif %} diff --git a/fedireads/templates/snippets/status_header.html b/fedireads/templates/snippets/status_header.html new file mode 100644 index 00000000..9650a034 --- /dev/null +++ b/fedireads/templates/snippets/status_header.html @@ -0,0 +1,21 @@ +{% load humanize %} +{% load fr_display %} +{% include 'snippets/avatar.html' with user=status.user %} +{% include 'snippets/username.html' with user=status.user %} + +{% if status.status_type == 'Update' %} + {{ status.content | safe }} +{% elif status.status_type == 'Review' %} + reviewed {{ status.book.title }} +{% elif status.status_type == 'Comment' %} + commented on {{ status.book.title }} +{% elif status.status_type == 'Boost' %} + boosted +{% elif status.reply_parent %} + {% with parent_status=status|parent %} + replied to {% include 'snippets/username.html' with user=parent_status.user possessive=True %} {{ parent_status.status_type | lower }} + {% endwith %} +{% endif %} + + {{ status.published_date | naturaltime }} + diff --git a/fedireads/templatetags/fr_display.py b/fedireads/templatetags/fr_display.py index 43a84b02..2594441c 100644 --- a/fedireads/templatetags/fr_display.py +++ b/fedireads/templatetags/fr_display.py @@ -101,6 +101,13 @@ def follow_request_exists(user, requester): return False +@register.filter(name='boosted_status') +def get_boosted(boost): + ''' load a boosted status. have to do this or it wont get foregin keys ''' + return models.Status.objects.select_subclasses().filter( + id=boost.boosted_status.id + ).get() + @register.simple_tag(takes_context=True) def shelve_button_identifier(context, book): From 575419cc5a9040e4d107a2b452f940e63a1c669c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 1 Apr 2020 15:15:21 -0700 Subject: [PATCH 09/14] Boost button shows if you boosted a post --- fedireads/templates/snippets/interaction.html | 4 ++-- fedireads/templatetags/fr_display.py | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/fedireads/templates/snippets/interaction.html b/fedireads/templates/snippets/interaction.html index 0e0a7d30..edc526d1 100644 --- a/fedireads/templates/snippets/interaction.html +++ b/fedireads/templates/snippets/interaction.html @@ -12,7 +12,7 @@ -

+ {% csrf_token %}
-
+ {% csrf_token %}
{% endfor %} + +
{% endblock %} diff --git a/fedireads/views.py b/fedireads/views.py index 3fcb51df..c8a69fe3 100644 --- a/fedireads/views.py +++ b/fedireads/views.py @@ -44,6 +44,12 @@ def home(request): @login_required def home_tab(request, tab): ''' user's homepage with activity feed ''' + page_size = 15 + try: + page = int(request.GET.get('page', 1)) + except ValueError: + page = 1 + shelves = [] shelves = get_user_shelf_preview( request.user, @@ -89,8 +95,11 @@ def home_tab(request, tab): # all activities from everyone you federate with activities = activities.filter(privacy='public') - activities = activities[:10] + activity_count = activities.count() + activities = activities[(page - 1) * page_size:page * page_size] + next_page = '/?page=%d' % (page + 1) + prev_page = '/?page=%d' % (page - 1) data = { 'user': request.user, 'shelves': shelves, @@ -104,6 +113,8 @@ def home_tab(request, tab): 'active_tab': tab, 'review_form': forms.ReviewForm(), 'comment_form': forms.CommentForm(), + 'next': next_page if activity_count > (page_size * page) else None, + 'prev': prev_page if page > 1 else None, } return TemplateResponse(request, 'feed.html', data) From 5f940ef62a103af1770d42270314b1578152a05b Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 1 Apr 2020 16:02:39 -0700 Subject: [PATCH 11/14] Helper function for getting activities --- fedireads/views.py | 82 ++++++++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/fedireads/views.py b/fedireads/views.py index c8a69fe3..4c6eaf3f 100644 --- a/fedireads/views.py +++ b/fedireads/views.py @@ -73,27 +73,7 @@ def home_tab(request, tab): # allows us to check if a user has shelved a book user_books = models.Edition.objects.filter(shelves__user=request.user).all() - # status updates for your follow network - following = models.User.objects.filter( - Q(followers=request.user) | Q(id=request.user.id) - ) - - activities = models.Status.objects.order_by( - '-created_date' - ).select_subclasses() - - if tab == 'home': - # people you follow and direct mentions - activities = activities.filter( - Q(user__in=following, privacy='public') | \ - Q(mention_users=request.user) - ) - elif tab == 'local': - # everyone on this instance - activities = activities.filter(user__local=True, privacy='public') - else: - # all activities from everyone you federate with - activities = activities.filter(privacy='public') + activities = get_activity_feed(request.user, tab) activity_count = activities.count() activities = activities[(page - 1) * page_size:page * page_size] @@ -119,6 +99,42 @@ def home_tab(request, tab): return TemplateResponse(request, 'feed.html', data) +def get_activity_feed(user, filter_level, model=models.Status): + ''' get a filtered queryset of statuses ''' + # status updates for your follow network + following = models.User.objects.filter( + Q(followers=user) | Q(id=user.id) + ) + + activities = model + if hasattr(model, 'objects'): + activities = model.objects + + activities = activities.order_by( + '-created_date' + ) + if hasattr(activities, 'select_subclasses'): + activities = activities.select_subclasses() + + # TODO: privacy relationshup between request.user and user + if filter_level in ['friends', 'home']: + # people you follow and direct mentions + activities = activities.filter( + Q(user__in=following, privacy='public') | \ + Q(mention_users=user) + ) + elif filter_level == 'self': + activities = activities.filter(user=user, privacy='public') + elif filter_level == 'local': + # everyone on this instance + activities = activities.filter(user__local=True, privacy='public') + else: + # all activities from everyone you federate with + activities = activities.filter(privacy='public') + + return activities + + def books_page(request): ''' discover books ''' recent_books = models.Work.objects @@ -199,11 +215,7 @@ def user_page(request, username, subpage=None): else: shelves = get_user_shelf_preview(user) data['shelves'] = shelves - activities = models.Status.objects.filter( - user=user, - ).order_by( - '-created_date', - ).select_subclasses().all()[:10] + activities = get_activity_feed(user, 'self')[:15] data['activities'] = activities return TemplateResponse(request, 'user.html', data) @@ -350,23 +362,7 @@ def book_page(request, book_identifier, tab='friends'): user=request.user, ).all() - if tab == 'friends': - reviews = book_reviews.filter( - Q(user__followers=request.user, privacy='public') | \ - Q(user=request.user) | \ - Q(mention_users=request.user), - ) - elif tab == 'local': - reviews = book_reviews.filter( - Q(privacy='public') | \ - Q(mention_users=request.user), - user__local=True, - ) - else: - reviews = book_reviews.filter( - Q(privacy='public') | \ - Q(mention_users=request.user), - ) + reviews = get_activity_feed(request.user, tab, model=book_reviews) try: # TODO: books can be on multiple shelves From ae37e5b9d924086fb4c016a4aa41198294970ed1 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 1 Apr 2020 16:18:58 -0700 Subject: [PATCH 12/14] Janky fix for z-index bug blocking review button --- fedireads/static/format.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fedireads/static/format.css b/fedireads/static/format.css index 34a5e64a..2215f80e 100644 --- a/fedireads/static/format.css +++ b/fedireads/static/format.css @@ -180,7 +180,7 @@ ul.menu a { flex-direction: column; padding-top: 70px; position: relative; - top: -50px; + top: -30px; z-index: 0; } From cfff75764b0f18aac1e1d743cb5f31d7724eab65 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 1 Apr 2020 16:21:11 -0700 Subject: [PATCH 13/14] avoid error on bad book data in discover page --- fedireads/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fedireads/views.py b/fedireads/views.py index 4c6eaf3f..2b406913 100644 --- a/fedireads/views.py +++ b/fedireads/views.py @@ -143,7 +143,7 @@ def books_page(request): if request.user.is_authenticated: recent_books = models.Edition.objects.filter( ~Q(shelfbook__shelf__user=request.user), - id__in=[b.id for b in recent_books], + id__in=[b.id for b in recent_books if b], ) data = { From 976e7ddf797f99a9fe32e47350e034b591dd7801 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 1 Apr 2020 19:38:07 -0700 Subject: [PATCH 14/14] CSS reorg and cleans up ui Fixes #93 --- fedireads/connectors/openlibrary.py | 2 +- fedireads/routine_book_tasks.py | 2 +- fedireads/static/format.css | 326 +++++++++--------- fedireads/templates/author.html | 13 +- fedireads/templates/book.html | 72 ++-- fedireads/templates/layout.html | 10 +- fedireads/templates/snippets/book.html | 18 - .../templates/snippets/book_description.html | 7 + .../templates/snippets/book_titleby.html | 9 + .../templates/snippets/covers_shelf.html | 12 +- fedireads/templates/snippets/status.html | 4 +- .../templates/snippets/status_content.html | 74 ++-- fedireads/views.py | 8 + 13 files changed, 302 insertions(+), 255 deletions(-) delete mode 100644 fedireads/templates/snippets/book.html create mode 100644 fedireads/templates/snippets/book_description.html create mode 100644 fedireads/templates/snippets/book_titleby.html diff --git a/fedireads/connectors/openlibrary.py b/fedireads/connectors/openlibrary.py index dd68920d..b58585bc 100644 --- a/fedireads/connectors/openlibrary.py +++ b/fedireads/connectors/openlibrary.py @@ -203,7 +203,7 @@ def set_default_edition(work): options = [e for e in options if e.cover] or options options = sorted( options, - key=lambda e: e.published_date.year if e.published_date else None + key=lambda e: e.published_date.year if e.published_date else 3000 ) if len(options): options[0].default = True diff --git a/fedireads/routine_book_tasks.py b/fedireads/routine_book_tasks.py index b35ccf4d..6c96ef3f 100644 --- a/fedireads/routine_book_tasks.py +++ b/fedireads/routine_book_tasks.py @@ -7,7 +7,7 @@ from fedireads import models def sync_book_data(): ''' update books with any changes to their canonical source ''' expiry = timezone.now() - timedelta(days=1) - books = models.Book.objects.filter( + books = models.Edition.objects.filter( sync=True, last_sync_date__lte=expiry ).all() diff --git a/fedireads/static/format.css b/fedireads/static/format.css index 2215f80e..0c1395c2 100644 --- a/fedireads/static/format.css +++ b/fedireads/static/format.css @@ -1,57 +1,55 @@ /* some colors that are okay: #247BA0 #70C1B2 #B2DBBF #F3FFBD #FF1654 */ + +/* general override */ * { margin: 0; padding: 0; line-height: 1.3em; font-family: sans-serif; } - html { background-color: #FFF; color: black; } -body { - padding-top: 90px; -} - a { color: #247BA0; } -input, button { - padding: 0.2em 0.5em; -} -button { - cursor: pointer; - width: max-content; -} - -h1, h2, h3, h4 { - font-weight: normal; -} - h1 { + font-weight: normal; font-size: 1.5rem; } - h2 { + font-weight: normal; font-size: 1rem; padding: 0.5rem 0.2rem; margin-bottom: 1rem; border-bottom: 3px solid #B2DBBF; } +h2 .edit-link { + text-decoration: none; + font-size: 0.9em; + float: right; +} +h2 .edit-link .icon { + font-size: 1.2em; +} h3 { font-size: 1rem; - margin: 1rem 0 0.5rem 0; - border-bottom: 3px solid #70C1B2; font-weight: bold; + margin-bottom: 0.5em; } h3 small { font-weight: normal; } + +/* fixed display top bar */ +body { + padding-top: 90px; +} #top-bar { overflow: visible; padding: 0.5rem; @@ -65,18 +63,31 @@ h3 small { z-index: 2; } -#warning { - background-color: #FF1654; +/* --- header bar content */ +#branding { + flex-grow: 0; +} +#menu { + list-style: none; text-align: center; + margin-top: 1.5rem; + flex-grow: 2; + font-size: 0.9em; +} +#menu li { + display: inline-block; + padding: 0 0.5em; + text-transform: uppercase; +} +#menu a { + color: #555; + text-decoration: none; + font-size: 0.9em; } -#branding a { - text-decoration: none; -} #actions { margin-top: 1em; } - #actions > * { display: inline-block; } @@ -106,7 +117,6 @@ h3 small { height: 1rem; width: 1rem; } - .notification { margin-bottom: 1em; padding: 1em 0; @@ -116,11 +126,6 @@ h3 small { background-color: #DDD; } - -button .icon { - font-size: 1.1rem; - vertical-align: sub; -} #search button { border: none; background: none; @@ -131,29 +136,6 @@ button .icon { max-width: 55rem; padding-right: 1em; } -header { - display: flex; - flex-direction: row; -} - -ul.menu { - list-style: none; - text-align: center; - margin-top: 1.5rem; - flex-grow: 1; - font-size: 0.9em; -} -ul.menu li { - display: inline-block; - background-color: white; - padding: 0 0.5em; - text-transform: uppercase; -} -ul.menu a { - color: #555; - text-decoration: none; - font-size: 0.9em; -} .pulldown-container { position: relative; @@ -175,15 +157,24 @@ ul.menu a { margin-bottom: 0.5em; } +/* content area */ +.content-container { + margin: 1rem; +} +.content-container > * { + padding-left: 1em; + padding-right: 1em; +} + #feed { display: flex; flex-direction: column; padding-top: 70px; position: relative; - top: -30px; z-index: 0; } +/* row component */ .row { display: flex; flex-direction: row; @@ -204,6 +195,16 @@ ul.menu a { flex-wrap: wrap; } +.column { + display: flex; + flex-direction: column; +} +.column > * { + margin-bottom: 1em; +} + + +/* discover books page grid of covers */ .book-grid .book-cover { height: 11em; width: auto; @@ -212,6 +213,17 @@ ul.menu a { margin-bottom: 2em; } +/* special case forms */ +.review-form label { + display: block; +} +.review-form textarea { + width: 30rem; + height: 10rem; +} + + + .follow-requests .row { margin-bottom: 0.5em; } @@ -219,6 +231,7 @@ ul.menu a { width: 20em; } + .login form { margin-top: 1em; } @@ -247,14 +260,14 @@ ul.menu a { .book-form .row label { width: max-content; } -form input { - flex-grow: 1; + +/* general form stuff */ +input, button { + padding: 0.2em 0.5em; } -form div { - margin-bottom: 1em; -} -textarea { - padding: 0.5em; +button, input[type="submit"] { + cursor: pointer; + width: max-content; } .content-container button { border: none; @@ -273,6 +286,36 @@ button.warning { background-color: #FF1654; } +form input { + flex-grow: 1; +} +form div { + margin-bottom: 1em; +} +textarea { + padding: 0.5em; +} + + +/* icons */ +a .icon { + color: black; + text-decoration: none; +} +button .icon { + font-size: 1.1rem; + vertical-align: sub; +} +.hidden-text { + height: 0; + width: 0; + position: absolute; + overflow: hidden; +} + + + +/* re-usable tab styles */ .tabs { display: flex; flex-direction: row; @@ -299,6 +342,7 @@ button.warning { color: black; } + .user-pic { width: 2em; height: 2em; @@ -312,51 +356,54 @@ button.warning { height: 5em; } -h2 .edit-link { - text-decoration: none; - font-size: 0.9em; - float: right; -} -h2 .edit-link .icon { - font-size: 1.2em; -} + .user-profile .row > * { flex-grow: 0; } .user-profile .row > *:last-child { flex-grow: 1; + margin-left: 2em; } -.review-form label { - display: block; -} - -.time-ago { - float: right; - display: block; - text-align: right; -} - +/* general book display */ .book-preview { overflow: hidden; z-index: 1; } - -.book-preview img { - float: left; - margin-right: 1em; -} - .book-preview.grid { float: left; } -.content-container { - margin: 1rem; +.cover-container { + flex-grow: 0; } -.content-container > * { - padding-left: 1em; - padding-right: 1em; +.cover-container button { + display: block; + margin: 0 auto; +} +.book-cover { + width: 180px; + height: auto; +} +.book-cover.small { + width: 50px; + height: auto; +} + +.no-cover { + position: relative; +} +.no-cover div { + position: absolute; + padding: 1em; + color: white; + top: 0; + left: 0; + text-align: center; +} +.no-cover .title { + text-transform: uppercase; + margin-bottom: 1em; } .all-shelves { @@ -379,51 +426,32 @@ h2 .edit-link .icon { padding-left: 1em; } -.user-shelves .covers-shelf { - flex-wrap: wrap; -} -.user-shelves > div { - margin: 1em 0; - padding: 0; -} -.user-shelves > div > * { - padding-left: 1em; -} -.user-shelves .covers-shelf .book-cover { - height: 9em; -} - .covers-shelf { display: flex; flex-direction: row; } -.covers-shelf .book-preview { +.covers-shelf .cover-container { margin-right: 1em; font-size: 0.9em; overflow: unset; width: min-content; position: relative; } -.covers-shelf .book-preview button { - display: block; - margin: 0 auto; - border: none; -} -.covers-shelf .book-preview:last-child { +.covers-shelf .cover-container:last-child { margin-right: 0; } -.covers-shelf .book-preview:hover { +.covers-shelf img:hover { cursor: pointer; -} -.covers-shelf .book-preview:hover img { box-shadow: #F3FFBD 0em 0em 1em 1em; } .covers-shelf .book-cover { - float: none; height: 11rem; width: auto; margin: 0; } +.covers-shelf button { + border: none; +} .close { float: right; @@ -437,29 +465,6 @@ h2 .edit-link .icon { .compose-suggestion.visible { display: block; } - -.no-cover { - position: relative; -} -.no-cover div { - position: absolute; - padding: 1em; - color: white; - top: 0; - left: 0; - text-align: center; -} -.no-cover .title { - text-transform: uppercase; - margin-bottom: 1em; -} -.book-cover { - width: 180px; -} -.book-cover.small { - width: 50px; - height: auto; -} .compose-suggestion .book-preview { background-color: #EEE; padding: 1em; @@ -476,14 +481,8 @@ h2 .edit-link .icon { display: inline; } -.review-form textarea { - width: 30rem; - height: 10rem; -} - blockquote { white-space: pre-line; - margin-left: 2em; } blockquote .icon-quote-open { float: left; @@ -557,8 +556,11 @@ th, td { color: #FF1654; } -.comment-thread .reply h2 { - background: none; +/* status css */ +.time-ago { + float: right; + display: block; + text-align: right; } .post { background-color: #EFEFEF; @@ -585,7 +587,10 @@ th, td { display: none; } - +/* status page with replies */ +.comment-thread .reply h2 { + background: none; +} .comment-thread .post { margin-left: 4em; border-left: 2px solid #247BA0; @@ -604,25 +609,16 @@ th, td { margin-left: 3em; } -a .icon { - color: black; - text-decoration: none; -} - -.hidden-text { - height: 0; - width: 0; - position: absolute; - overflow: hidden; -} - -.book-row .book-cover { - float: none; -} - +/* pagination */ .pagination a { text-decoration: none; } .pagination .next { text-align: right; } + +/* special one-off "delete all data" banner */ +#warning { + background-color: #FF1654; + text-align: center; +} diff --git a/fedireads/templates/author.html b/fedireads/templates/author.html index a240ada7..cc06cccf 100644 --- a/fedireads/templates/author.html +++ b/fedireads/templates/author.html @@ -13,11 +13,16 @@

Books by {{ author.name }}

- {% for book in books %} -
- {% include 'snippets/book.html' with book=book size=large description=True %} +
+ {% for book in books %} +
+ + {% include 'snippets/book_cover.html' with book=book %} + + {% include 'snippets/shelve_button.html' with book=book %} +
+ {% endfor %}
- {% endfor %}
{% endblock %} diff --git a/fedireads/templates/book.html b/fedireads/templates/book.html index 21cfbbe7..137d996b 100644 --- a/fedireads/templates/book.html +++ b/fedireads/templates/book.html @@ -1,9 +1,9 @@ {% extends 'layout.html' %} {% load fr_display %} {% block content %} -