From 4765f965cf92f6676b7692036750dedda5ad4661 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Fri, 13 Mar 2020 17:57:36 -0700 Subject: [PATCH] Combine html and json views Fixes #80 --- fedireads/incoming.py | 95 ++-------------------------------- fedireads/urls.py | 31 +++++++---- fedireads/views.py | 116 +++++++++++++++++++++++++++++++++++++----- 3 files changed, 128 insertions(+), 114 deletions(-) diff --git a/fedireads/incoming.py b/fedireads/incoming.py index 2d3b1bdb..b7f874cb 100644 --- a/fedireads/incoming.py +++ b/fedireads/incoming.py @@ -122,96 +122,6 @@ def inbox(request, username): return shared_inbox(request) -@csrf_exempt -def get_actor(request, username): - ''' return an activitypub actor object ''' - if request.method != 'GET': - return HttpResponseBadRequest() - - try: - user = models.User.objects.get(localname=username) - except models.User.DoesNotExist: - return HttpResponseNotFound() - return JsonResponse(activitypub.get_actor(user)) - - -@csrf_exempt -def get_status(request, username, status_id): - ''' return activity json for a specific status ''' - if request.method != 'GET': - return HttpResponseBadRequest() - - try: - user = models.User.objects.get(localname=username) - status = models.Status.objects.get(id=status_id) - except ValueError: - return HttpResponseNotFound() - - if user != status.user: - return HttpResponseNotFound() - - return JsonResponse(activitypub.get_status(status)) - - -@csrf_exempt -def get_replies(request, username, status_id): - ''' ordered collection of replies to a status ''' - # TODO: this isn't a full implmentation - if request.method != 'GET': - return HttpResponseBadRequest() - - status = models.Status.objects.get(id=status_id) - if status.user.localname != username: - return HttpResponseNotFound() - - replies = models.Status.objects.filter( - reply_parent=status, - ).select_subclasses() - - if request.GET.get('only_other_accounts'): - replies = replies.filter( - ~Q(user=status.user) - ) - else: - replies = replies.filter(user=status.user) - - if request.GET.get('page'): - min_id = request.GET.get('min_id') - if min_id: - replies = replies.filter(id__gt=min_id) - max_id = request.GET.get('max_id') - if max_id: - replies = replies.filter(id__lte=max_id) - activity = activitypub.get_replies_page(status, replies) - return JsonResponse(activity) - - return JsonResponse(activitypub.get_replies(status, replies)) - - -@csrf_exempt -def get_followers(request, username): - ''' return a list of followers for an actor ''' - if request.method != 'GET': - return HttpResponseBadRequest() - - user = models.User.objects.get(localname=username) - followers = user.followers - page = request.GET.get('page') - return JsonResponse(activitypub.get_followers(user, page, followers)) - - -@csrf_exempt -def get_following(request, username): - ''' return a list of following for an actor ''' - if request.method != 'GET': - return HttpResponseBadRequest() - - user = models.User.objects.get(localname=username) - following = user.following - page = request.GET.get('page') - return JsonResponse(activitypub.get_following(user, page, following)) - - def handle_incoming_follow(activity): ''' someone wants to follow a local user ''' # figure out who they want to follow @@ -263,7 +173,10 @@ def handle_incoming_follow_accept(activity): accepter = get_or_create_remote_user(activity['actor']) try: - request = models.UserFollowRequest.objects.get(user_subject=requester, user_object=accepter) + request = models.UserFollowRequest.objects.get( + user_subject=requester, + user_object=accepter + ) request.delete() except models.UserFollowRequest.DoesNotExist: pass diff --git a/fedireads/urls.py b/fedireads/urls.py index f3e6e4a7..6ff07e0c 100644 --- a/fedireads/urls.py +++ b/fedireads/urls.py @@ -6,8 +6,8 @@ from django.urls import path, re_path from fedireads import incoming, outgoing, views, settings, wellknown from fedireads import view_actions as actions -username_regex = r'(?P[\w@\.-]+)' -localname_regex = r'(?P[\w\.-]+)' +username_regex = r'(?P[\w@\-_]+)' +localname_regex = r'(?P[\w\-_]+)' user_path = r'^user/%s' % username_regex local_user_path = r'^user/%s' % localname_regex status_path = r'%s/(status|review)/(?P\d+)' % local_user_path @@ -17,14 +17,8 @@ urlpatterns = [ # federation endpoints re_path(r'^inbox/?$', incoming.shared_inbox), - re_path(r'%s.json$' % local_user_path, incoming.get_actor), re_path(r'%s/inbox/?$' % local_user_path, incoming.inbox), re_path(r'%s/outbox/?$' % local_user_path, outgoing.outbox), - re_path(r'%s/followers/?$' % local_user_path, incoming.get_followers), - re_path(r'%s/following/?$' % local_user_path, incoming.get_following), - re_path(r'%s.json$' % status_path, incoming.get_status), - re_path(r'%s/activity/?$' % status_path, incoming.get_status), - re_path(r'%s/replies/?$' % status_path, incoming.get_replies), # .well-known endpoints re_path(r'^.well-known/webfinger/?$', wellknown.webfinger), @@ -34,15 +28,32 @@ urlpatterns = [ # TODO: re_path(r'^.well-known/host-meta/?$', incoming.host_meta), # ui views - path('', views.home), - re_path(r'^(?Phome|local|federated)/?$', views.home_tab), re_path(r'^register/?$', views.register), re_path(r'^login/?$', views.user_login), re_path(r'^logout/?$', views.user_logout), + + # should return a ui view or activitypub json blob as requested + path('', views.home), + re_path(r'^(?Phome|local|federated)/?$', views.home_tab), re_path(r'^notifications/?', views.notifications_page), + + # users re_path(r'%s/?$' % user_path, views.user_page), + re_path(r'%s\.json$' % local_user_path, views.user_page), re_path(r'edit_profile_page/?$', views.edit_profile_page), + re_path(r'%s/followers/?$' % local_user_path, views.followers_page), + re_path(r'%s/followers.json$' % local_user_path, views.followers_page), + re_path(r'%s/following/?$' % local_user_path, views.following_page), + re_path(r'%s/following.json$' % local_user_path, views.following_page), + + # statuses re_path(r'%s/?$' % status_path, views.status_page), + re_path(r'%s.json$' % status_path, views.status_page), + re_path(r'%s/activity/?$' % status_path, views.status_page), + re_path(r'%s/replies/?$' % status_path, views.replies_page), + re_path(r'%s/replies\.json$' % status_path, views.replies_page), + + # books re_path(r'^book/(?P\w+)/?$', views.book_page), re_path(r'^book/(?P\w+)/(?Pfriends|local|federated)?$', views.book_page), re_path(r'^author/(?P\w+)/?$', views.author_page), diff --git a/fedireads/views.py b/fedireads/views.py index 8c87ad8d..cb45971a 100644 --- a/fedireads/views.py +++ b/fedireads/views.py @@ -2,11 +2,14 @@ from django.contrib.auth import authenticate, login, logout from django.contrib.auth.decorators import login_required from django.db.models import Avg, Q -from django.http import HttpResponseNotFound +from django.http import HttpResponseBadRequest, HttpResponseNotFound, \ + JsonResponse from django.shortcuts import redirect from django.template.response import TemplateResponse +from django.views.decorators.csrf import csrf_exempt -from fedireads import forms, models, books_manager, incoming +from fedireads import activitypub +from fedireads import forms, models, books_manager from fedireads.settings import DOMAIN @@ -19,6 +22,14 @@ def get_user_from_username(username): return user + +def is_api_request(request): + ''' check whether a request is asking for html or data ''' + # TODO: this should probably be the full content type? maybe? + return 'json' in request.headers.get('Accept') or \ + request.path[-5:] == '.json' + + @login_required def home(request): ''' this is the same as the feed on the home tab ''' @@ -153,20 +164,19 @@ def notifications_page(request): return TemplateResponse(request, 'notifications.html', data) +@csrf_exempt def user_page(request, username): ''' profile page for a user ''' - content = request.headers.get('Accept') - # TODO: this should probably be the full content type? maybe? - if 'json' in content: - # we have a json request - return incoming.get_actor(request, username) - - # otherwise we're at a UI view try: user = get_user_from_username(username) except models.User.DoesNotExist: return HttpResponseNotFound() + if is_api_request(request): + # we have a json request + return JsonResponse(activitypub.get_actor(user)) + # otherwise we're at a UI view + # TODO: change display with privacy and authentication considerations shelves = models.Shelf.objects.filter(user=user) ratings = {r.book.id: r.rating for r in \ @@ -181,12 +191,52 @@ def user_page(request, username): return TemplateResponse(request, 'user.html', data) +@csrf_exempt +def followers_page(request, username): + ''' list of followers ''' + if request.method != 'GET': + return HttpResponseBadRequest() + + try: + user = get_user_from_username(username) + except models.User.DoesNotExist: + return HttpResponseNotFound() + + if is_api_request(request): + user = models.User.objects.get(localname=username) + followers = user.followers + page = request.GET.get('page') + return JsonResponse(activitypub.get_followers(user, page, followers)) + + return redirect('/user/' + username) + + +@csrf_exempt +def following_page(request, username): + ''' list of followers ''' + if request.method != 'GET': + return HttpResponseBadRequest() + + try: + user = get_user_from_username(username) + except models.User.DoesNotExist: + return HttpResponseNotFound() + + if is_api_request(request): + user = models.User.objects.get(localname=username) + following = user.following + page = request.GET.get('page') + return JsonResponse(activitypub.get_following(user, page, following)) + + return redirect('/user/' + username) + + +@csrf_exempt def status_page(request, username, status_id): ''' display a particular status (and replies, etc) ''' - content = request.headers.get('Accept') - if 'json' in content: - # we have a json request - return incoming.get_status(request, username, status_id) + if request.method != 'GET': + return HttpResponseBadRequest() + try: user = get_user_from_username(username) status = models.Status.objects.select_subclasses().get(id=status_id) @@ -196,12 +246,52 @@ def status_page(request, username, status_id): if user != status.user: return HttpResponseNotFound() + if is_api_request(request): + return JsonResponse(activitypub.get_status(status)) + data = { 'status': status, } return TemplateResponse(request, 'status.html', data) +@csrf_exempt +def replies_page(request, username, status_id): + ''' ordered collection of replies to a status ''' + if request.method != 'GET': + return HttpResponseBadRequest() + + if not is_api_request(request): + return status_page(request, username, status_id) + + status = models.Status.objects.get(id=status_id) + if status.user.localname != username: + return HttpResponseNotFound() + + replies = models.Status.objects.filter( + reply_parent=status, + ).select_subclasses() + + if request.GET.get('only_other_accounts'): + replies = replies.filter( + ~Q(user=status.user) + ) + else: + replies = replies.filter(user=status.user) + + if request.GET.get('page'): + min_id = request.GET.get('min_id') + if min_id: + replies = replies.filter(id__gt=min_id) + max_id = request.GET.get('max_id') + if max_id: + replies = replies.filter(id__lte=max_id) + activity = activitypub.get_replies_page(status, replies) + return JsonResponse(activity) + + return JsonResponse(activitypub.get_replies(status, replies)) + + @login_required def edit_profile_page(request): ''' profile page for a user '''