diff --git a/fedireads/activitypub/status.py b/fedireads/activitypub/status.py index 1f3a7f6f8..2e214e7cb 100644 --- a/fedireads/activitypub/status.py +++ b/fedireads/activitypub/status.py @@ -1,7 +1,7 @@ ''' status serializers ''' def get_review(review): ''' fedireads json for book reviews ''' - status = get_status_json(review) + status = get_status(review) status['inReplyTo'] = review.book.absolute_id status['fedireadsType'] = review.status_type, status['name'] = review.name diff --git a/fedireads/forms.py b/fedireads/forms.py index 5a5948be8..75bc56ab2 100644 --- a/fedireads/forms.py +++ b/fedireads/forms.py @@ -40,8 +40,17 @@ class ReviewForm(ModelForm): } +class CommentForm(ModelForm): + class Meta: + model = models.Status + fields = ['content'] + help_texts = {f: None for f in fields} + labels = {'content': 'Comment'} + + class EditUserForm(ModelForm): class Meta: model = models.User fields = ['avatar', 'name', 'summary'] help_texts = {f: None for f in fields} + diff --git a/fedireads/incoming.py b/fedireads/incoming.py index 0210ddec1..579e0d3ba 100644 --- a/fedireads/incoming.py +++ b/fedireads/incoming.py @@ -14,6 +14,7 @@ from fedireads import models from fedireads import outgoing from fedireads.status import create_review, create_status from fedireads.remote_user import get_or_create_remote_user +from fedireads.settings import DOMAIN @csrf_exempt @@ -133,6 +134,34 @@ def get_status(request, username, status_id): return JsonResponse(activitypub.get_status(status)) +@csrf_exempt +def get_replies(request, username, status_id): + ''' ordered collection of replies to a status ''' + if request.method != 'GET': + return HttpResponseBadRequest() + + replies = models.Status.objects.filter( + reply_parent=status_id + ).first() + + path_id = 'https://%s/user/%s/status/%s/replies' % \ + (DOMAIN, username, status_id) + replies_activity = { + '@context': 'https://www.w3.org/ns/activitystreams', + 'id': path_id, + 'type': 'Collection', + 'first': { + 'id': '%s?page=true' % path_id, + 'type': 'CollectionPage', + 'next': '%s?only_other_accounts=true&page=true' % path_id, + 'partOf': path_id, + 'items': [replies.activity] + } + } + return JsonResponse(replies_activity) + + + @csrf_exempt def get_followers(request, username): ''' return a list of followers for an actor ''' diff --git a/fedireads/outgoing.py b/fedireads/outgoing.py index 53baa592f..86326f137 100644 --- a/fedireads/outgoing.py +++ b/fedireads/outgoing.py @@ -4,9 +4,9 @@ from django.views.decorators.csrf import csrf_exempt import requests from urllib.parse import urlencode +from fedireads import activitypub from fedireads import models from fedireads.status import create_review, create_status -from fedireads import activitypub from fedireads.remote_user import get_or_create_remote_user from fedireads.broadcast import get_recipients, broadcast @@ -135,3 +135,14 @@ def handle_review(user, book, name, content, rating): recipients = get_recipients(user, 'public') broadcast(user, create_activity, recipients) + +def handle_comment(user, review, content): + ''' post a review ''' + # validated and saves the comment in the database so it has an id + comment = create_status(user, content, reply_parent=review) + comment_activity = activitypub.get_status(comment) + create_activity = activitypub.get_create(user, comment_activity) + + recipients = get_recipients(user, 'public') + broadcast(user, create_activity, recipients) + diff --git a/fedireads/status.py b/fedireads/status.py index ad30319ea..2114e94d7 100644 --- a/fedireads/status.py +++ b/fedireads/status.py @@ -9,10 +9,7 @@ def create_review(user, possible_book, name, content, rating): # throws a value error if the book is not found book = get_or_create_book(possible_book) - # sanitize review html - parser = InputHtmlParser() - parser.feed(content) - content = parser.get_output() + content = sanitize(content) # no ratings outside of 0-5 rating = rating if 0 <= rating <= 5 else 0 @@ -41,8 +38,15 @@ def create_status(user, content, reply_parent=None, mention_books=None): reply_parent=reply_parent, ) - for book in mention_books: - status.mention_books.add(book) + if mention_books: + for book in mention_books: + status.mention_books.add(book) return status + +def sanitize(content): + ''' remove invalid html from free text ''' + parser = InputHtmlParser() + parser.feed(content) + return parser.get_output() diff --git a/fedireads/templates/feed.html b/fedireads/templates/feed.html index 9fb3d1e96..f18e107a8 100644 --- a/fedireads/templates/feed.html +++ b/fedireads/templates/feed.html @@ -66,7 +66,15 @@

{{ activity.rating | stars }}

{{ activity.content | safe }}

-
+
+ +
+ {% csrf_token %} + + {{ comment_form.content }} + +
+
{% elif activity.status_type == 'Note' %} posted {{ activity.content | safe }} diff --git a/fedireads/urls.py b/fedireads/urls.py index a601b644f..b0ff24a55 100644 --- a/fedireads/urls.py +++ b/fedireads/urls.py @@ -24,7 +24,10 @@ urlpatterns = [ r'^user/(?P\w+)/(status|review)/(?P\d+)/activity/?$', incoming.get_status ), - re_path(r'^user/(?P\w+)/(status|review)/?$', incoming.get_following), + re_path( + r'^user/(?P\w+)/(status|review)/(?P\d+)/replies/?$', + incoming.get_replies + ), # TODO: shelves need pages in the UI and for their activitypub Collection # .well-known endpoints @@ -47,6 +50,7 @@ urlpatterns = [ # internal action endpoints re_path(r'^review/?$', views.review), + re_path(r'^comment/?$', views.comment), re_path( r'^shelve/(?P\w+)/(?P[\w-]+)/(?P\d+)/?$', views.shelve diff --git a/fedireads/views.py b/fedireads/views.py index 74e5a2a53..c68b75f0c 100644 --- a/fedireads/views.py +++ b/fedireads/views.py @@ -43,6 +43,7 @@ def home(request): '-created_date' )[:10] + comment_form = forms.CommentForm() data = { 'user': request.user, 'reading': reading, @@ -50,6 +51,7 @@ def home(request): 'recent_books': recent_books, 'user_books': user_books, 'activities': activities, + 'comment_form': comment_form, } return TemplateResponse(request, 'feed.html', data) @@ -250,6 +252,18 @@ def review(request): return redirect('/book/%s' % book_identifier) +def comment(request): + ''' respond to a book review ''' + form = forms.CommentForm(request.POST) + # this is a bit of a formality, the form is just one text field + if not form.is_valid(): + return redirect('/') + review_id = request.POST['review'] + parent = models.Review.objects.get(id=review_id) + outgoing.handle_comment(request.user, parent, form.data['content']) + return redirect('/') + + @login_required def follow(request): ''' follow another user, here or abroad '''