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/connectors/openlibrary.py b/fedireads/connectors/openlibrary.py index c3653ab5..b07fd17d 100644 --- a/fedireads/connectors/openlibrary.py +++ b/fedireads/connectors/openlibrary.py @@ -188,9 +188,10 @@ class Connector(AbstractConnector): } author = update_from_mappings(author, data, mappings) # TODO this is making some BOLD assumption - name = data['name'] - author.last_name = name.split(' ')[-1] - author.first_name = ' '.join(name.split(' ')[:-1]) + name = data.get('name') + if name: + author.last_name = name.split(' ')[-1] + author.first_name = ' '.join(name.split(' ')[:-1]) author.save() return author @@ -223,10 +224,11 @@ 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 ) - 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/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..91ec0ab5 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): @@ -100,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/outgoing.py b/fedireads/outgoing.py index 098b0784..73e373db 100644 --- a/fedireads/outgoing.py +++ b/fedireads/outgoing.py @@ -163,6 +163,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: @@ -201,10 +205,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/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 f0b5e1a8..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: -50px; 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,9 +342,10 @@ button.warning { color: black; } + .user-pic { - width: 2rem; - height: 2rem; + width: 2em; + height: 2em; border-radius: 50%; vertical-align: top; position: relative; @@ -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; @@ -577,7 +579,18 @@ 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; +} +/* status page with replies */ +.comment-thread .reply h2 { + background: none; +} .comment-thread .post { margin-left: 4em; border-left: 2px solid #247BA0; @@ -596,14 +609,16 @@ th, td { margin-left: 3em; } -a .icon { - color: black; +/* pagination */ +.pagination a { text-decoration: none; } - -.hidden-text { - height: 0; - width: 0; - position: absolute; - overflow: hidden; +.pagination .next { + text-align: right; +} + +/* special one-off "delete all data" banner */ +#warning { + background-color: #FF1654; + text-align: center; } 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..1859c4af 100644 --- a/fedireads/static/js/shared.js +++ b/fedireads/static/js/shared.js @@ -1,3 +1,15 @@ +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'; + } + + 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/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/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 %} -
-

{{ book.title }} by - {% include 'snippets/authors.html' with book=book %} +
+

+ {% include 'snippets/book_titleby.html' with book=book %} {% if request.user.is_authenticated %} edit @@ -13,27 +13,14 @@ {% endif %}

-
-
+
+ +
{% include 'snippets/book_cover.html' with book=book size=large %} -

{{ active_tab }} rating: {{ rating | stars }}

- {% if book.parent_work.description %} -
{{ book.parent_work.description | description }}
- {% endif %} -
-
- {% for tag in tags %} - {% include 'snippets/tag.html' with tag=tag user=request.user user_tags=user_tag_names %} - {% endfor %} -
-
-

{{ book.parent_work.edition_set.count }} other editions

{% include 'snippets/shelve_button.html' %} -
-
{% if request.user.is_authenticated and not book.cover %}
{% csrf_token %} @@ -41,21 +28,48 @@
{% endif %} + +
+ {% for field in info_fields %} + {% if field.value %} +
{{ field.name }}:
+
{{ field.value }}
+ {% endif %} + {% endfor %} +
+ +
+

{{ active_tab }} rating: {{ rating | stars }}

+ + {% include 'snippets/book_description.html' %} + + {% if book.parent_work.edition_set.count > 1 %} +

{{ book.parent_work.edition_set.count }} editions

+ {% endif %} + +
+ {% for tag in tags %} + {% include 'snippets/tag.html' with tag=tag user=request.user user_tags=user_tag_names %} + {% endfor %} +
+ +
+ {% if request.user.is_authenticated %} +

Leave a review

+
+ {% csrf_token %} + + {{ review_form.as_p }} + +
+ {% endif %} +
-{% if request.user.is_authenticated %} -
-

Leave a review

-
- {% csrf_token %} - - {{ review_form.as_p }} - -
-
+{% if request.user.is_authenticated %}
{% include 'snippets/tabs.html' with tabs=feed_tabs active_tab=active_tab path=path %}
diff --git a/fedireads/templates/feed.html b/fedireads/templates/feed.html index 87e7fc2a..96731ffe 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 %}
@@ -29,7 +15,26 @@ {% include 'snippets/status.html' with status=activity %}
{% endfor %} + +
- {% endblock %} diff --git a/fedireads/templates/layout.html b/fedireads/templates/layout.html index 22dfddbd..4dcf1ac0 100644 --- a/fedireads/templates/layout.html +++ b/fedireads/templates/layout.html @@ -22,10 +22,14 @@
-
-
+
+
+ + + +
-