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 @@