Porównaj commity

...

5 Commity

Autor SHA1 Wiadomość Data
Marnanel Thurman 94309cc77d KepiTestCase
The only difference from django.test.TestCase is that KepiTestCase
redirects logging to stdout in setUp, and undoes it again in
tearDown. This is because Django's "./manage.py test" suppresses
stderr, so you can't read the debug logs otherwise.

Put into use in a couple of files where it was needed immediately.
Will add more later.
2023-09-22 16:36:51 +01:00
Marnanel Thurman b00f844d84 Follow -> FollowUser; Unfollow -> UnfollowUser
Since k.t.models also has a Follow class, it was getting confusing.

urls also doesn't "import *" the views classes any more, for clarity.

There were some classes duplicated between persons.py and statuses.py;
they have been confined to persons.py.

__init__.py has content.
2023-09-22 16:36:51 +01:00
Marnanel Thurman 8a0deb6c93 AppConfigs set default_auto_field
Django used to default this, but it generates a warning now.
2023-09-22 16:36:47 +01:00
Marnanel Thurman 4d63fd669d Remove "providing_args" from signals
This will probably break them, but we don't need them at present.
2023-09-22 16:36:22 +01:00
Thomas Thurman 07875e03ab config error, marnanel.org specific 2023-09-22 16:36:22 +01:00
12 zmienionych plików z 103 dodań i 315 usunięć

Wyświetl plik

@ -1,5 +1,3 @@
from django.dispatch import Signal
received = Signal(
providing_args=[
])
received = Signal()

Wyświetl plik

@ -19,4 +19,5 @@ urlpatterns = [
path('users/<str:username>/following', bowler_pub_views.FollowingView.as_view()),
path('users/<str:username>/featured', bowler_pub_views.FeaturedView.as_view()),
path('sharedInbox', bowler_pub_views.InboxView.as_view()),
path('inbox', bowler_pub_views.InboxView.as_view()), # config error, marnanel.org specific
]

Wyświetl plik

@ -0,0 +1,21 @@
import logging
import sys
import django
logger = logging.getLogger('kepi')
class KepiTestCase(django.test.TestCase):
"""
A test case.
It turns on logging to stdout.
"""
def setUp(self):
super().setUp()
self._logging_stream_handler = logging.StreamHandler(sys.stdout)
logger.addHandler(self._logging_stream_handler)
def tearDown(self):
super().tearDown()
logger.removeHandler(self._logging_stream_handler)

Wyświetl plik

@ -0,0 +1,5 @@
import django.apps
class SombreroApiConfig(django.apps.AppConfig):
name = 'kepi.sombrero_sendpub'
default_auto_field = 'django.db.models.AutoField'

Wyświetl plik

@ -8,12 +8,12 @@ import logging
logger = logging.getLogger(name="kepi")
from unittest import skip
from django.test import TestCase
from django.conf import settings
from kepi.sombrero_sendpub.fetch import fetch
from kepi.trilby_api.models import RemotePerson, Person, Status
from kepi.trilby_api.tests import create_local_person
from kepi.sombrero_sendpub.collections import Collection
from kepi.kepi.testing import KepiTestCase
from . import suppress_thread_exceptions
import httpretty
import requests
@ -166,7 +166,7 @@ EXAMPLE_COMPLEX_COLLECTION_PAGE_2 = """{
EXAMPLE_COMPLEX_COLLECTION_URL,
)
class TestFetchRemoteUser(TestCase):
class TestFetchRemoteUser(KepiTestCase):
@httpretty.activate
def test_fetch(self):
@ -527,9 +527,10 @@ class TestFetchRemoteUser(TestCase):
len(EXAMPLE_COMPLEX_COLLECTION_MEMBERS),
msg="Collection has a length")
class TestFetchLocalUser(TestCase):
class TestFetchLocalUser(KepiTestCase):
def setUp(self):
super().setUp()
self._alice = create_local_person(
name = 'alice',
)
@ -588,5 +589,5 @@ class TestFetchLocalUser(TestCase):
None,
)
class TestFetchStatus(TestCase):
class TestFetchStatus(KepiTestCase):
pass

Wyświetl plik

@ -1,5 +1,5 @@
from django.apps import AppConfig
import django.apps
class TrilbyApiConfig(AppConfig):
class TrilbyApiConfig(django.apps.AppConfig):
name = 'kepi.trilby_api'
default_auto_field = 'django.db.models.AutoField'

Wyświetl plik

@ -9,33 +9,9 @@ logger = logging.getLogger(name='kepi')
from django.dispatch import Signal
liked = Signal(
providing_args=[
'liker',
'liked',
])
unliked = Signal(
providing_args=[
'liker',
'liked',
])
followed = Signal(
providing_args=[
'follower',
'followed',
])
unfollowed = Signal(
providing_args=[
'follower',
'followed',
])
posted = Signal(
providing_args=[
])
reblogged = Signal(
)
liked = Signal()
unliked = Signal()
followed = Signal()
unfollowed = Signal()
posted = Signal()
reblogged = Signal()

Wyświetl plik

@ -1,10 +1,12 @@
from django.test import TestCase
from django.conf import settings
from kepi.trilby_api.utils import *
from kepi.kepi.testing import KepiTestCase
class Tests(TestCase):
class Tests(KepiTestCase):
def setUp(self):
super().setUp()
settings.KEPI['LOCAL_OBJECT_HOSTNAME'] = 'testserver'
def test_is_local_user_url(self):

Wyświetl plik

@ -5,48 +5,48 @@
# Licensed under the GNU Public License v2.
from django.urls import path
from .views import *
import kepi.trilby_api.views as views
urlpatterns = [
path('api/v1/instance', Instance.as_view()),
path('api/v1/instance/', Instance.as_view()), # keep tootstream happy
path('api/v1/apps', Apps.as_view()),
path('api/v1/instance', views.Instance.as_view()),
path('api/v1/instance/', views.Instance.as_view()), # keep tootstream happy
path('api/v1/apps', views.Apps.as_view()),
path('api/v1/accounts/verify_credentials', VerifyCredentials.as_view()),
path('api/v1/accounts/verify_credentials', views.VerifyCredentials.as_view()),
path('api/v1/accounts/update_credentials',
UpdateCredentials.as_view()),
views.UpdateCredentials.as_view()),
path('api/v1/accounts/search', AccountsSearch.as_view()),
path('api/v1/accounts/search', views.AccountsSearch.as_view()),
path('api/v1/accounts/<user>', User.as_view()),
path('api/v1/accounts/<user>/statuses', Statuses.as_view()),
path('api/v1/accounts/<user>/following', Following.as_view()),
path('api/v1/accounts/<user>/followers', Followers.as_view()),
path('api/v1/accounts/<user>/follow', Follow.as_view()),
path('api/v1/accounts/<user>/unfollow', Unfollow.as_view()),
path('api/v1/accounts/<user>', views.User.as_view()),
path('api/v1/accounts/<user>/statuses', views.Statuses.as_view()),
path('api/v1/accounts/<user>/following', views.Following.as_view()),
path('api/v1/accounts/<user>/followers', views.Followers.as_view()),
path('api/v1/accounts/<user>/follow', views.FollowUser.as_view()),
path('api/v1/accounts/<user>/unfollow', views.UnfollowUser.as_view()),
path('api/v1/statuses', Statuses.as_view()),
path('api/v1/statuses/<status>', SpecificStatus.as_view()),
path('api/v1/statuses/<status>/context', StatusContext.as_view()),
path('api/v1/statuses', views.Statuses.as_view()),
path('api/v1/statuses/<status>', views.SpecificStatus.as_view()),
path('api/v1/statuses/<status>/context', views.StatusContext.as_view()),
# Favourite, aka like
path('api/v1/statuses/<status>/favourite', Favourite.as_view()),
path('api/v1/statuses/<status>/unfavourite', Unfavourite.as_view()),
path('api/v1/statuses/<status>/favourited_by', StatusFavouritedBy.as_view()),
path('api/v1/statuses/<status>/favourite', views.Favourite.as_view()),
path('api/v1/statuses/<status>/unfavourite', views.Unfavourite.as_view()),
path('api/v1/statuses/<status>/favourited_by', views.StatusFavouritedBy.as_view()),
# Reblog, aka boost
path('api/v1/statuses/<status>/reblog', Reblog.as_view()),
path('api/v1/statuses/<status>/unreblog', Unreblog.as_view()),
path('api/v1/statuses/<status>/reblogged_by', StatusRebloggedBy.as_view()),
path('api/v1/statuses/<status>/reblog', views.Reblog.as_view()),
path('api/v1/statuses/<status>/unreblog', views.Unreblog.as_view()),
path('api/v1/statuses/<status>/reblogged_by', views.StatusRebloggedBy.as_view()),
path('api/v1/notifications', Notifications.as_view()),
path('api/v1/filters', Filters.as_view()),
path('api/v1/custom_emojis', Emojis.as_view()),
path('api/v1/timelines/public', PublicTimeline.as_view()),
path('api/v1/timelines/home', HomeTimeline.as_view()),
path('api/v1/notifications', views.Notifications.as_view()),
path('api/v1/filters', views.Filters.as_view()),
path('api/v1/custom_emojis', views.Emojis.as_view()),
path('api/v1/timelines/public', views.PublicTimeline.as_view()),
path('api/v1/timelines/home', views.HomeTimeline.as_view()),
path('api/v1/search', Search.as_view()),
path('api/v1/search', views.Search.as_view()),
path('users/<username>/feed', UserFeed.as_view()),
path('users/<username>/feed', views.UserFeed.as_view()),
]

Wyświetl plik

@ -1,73 +1,38 @@
# trilby_api/views/__init__.py
#
# Part of kepi.
# Copyright (c) 2018-2021 Marnanel Thurman.
# Licensed under the GNU Public License v2.
from .other import \
Instance, \
Emojis, \
Filters, \
Search, \
AccountsSearch
from .statuses import \
Favourite, Unfavourite, \
Reblog, Unreblog, \
SpecificStatus, \
Statuses, \
StatusContext, \
StatusFavouritedBy, \
StatusRebloggedBy, \
Notifications
from .persons import \
Follow, Unfollow, \
UpdateCredentials, \
VerifyCredentials,\
User, \
Followers, Following
from .oauth import \
Apps, \
fix_oauth2_redirects
from .timelines import \
PublicTimeline, \
HomeTimeline, \
UserFeed
from .oauth import *
from .other import *
from .persons import *
from .statuses import *
from .timelines import *
__all__ = [
# other
'Instance',
'Emojis',
'Filters',
'Search',
'AbstractTimeline',
'AccountsSearch',
# statuses
'Favourite', 'Unfavourite',
'Reblog', 'Unreblog',
'Apps',
'DoSomethingWithPerson',
'DoSomethingWithStatus',
'Emojis',
'Favourite',
'Filters',
'FollowUser',
'Followers',
'Followers_or_Following',
'Following',
'HomeTimeline',
'Instance',
'Notifications',
'PublicTimeline',
'Reblog',
'Search',
'SpecificStatus',
'Statuses',
'StatusContext',
'Statuses',
'StatusFavouritedBy',
'StatusRebloggedBy',
'Notifications',
# persons
'Follow', 'Unfollow',
'Unfavourite',
'UnfollowUser',
'Unreblog',
'UpdateCredentials',
'VerifyCredentials',
'User',
'Followers', 'Following',
# oauth
'Apps',
'fix_oauth2_redirects',
# timelines
'PublicTimeline',
'HomeTimeline',
'UserFeed',
'VerifyCredentials',
]

Wyświetl plik

@ -71,7 +71,7 @@ class DoSomethingWithPerson(generics.GenericAPIView):
reason = 'Done',
)
class Follow(DoSomethingWithPerson):
class FollowUser(DoSomethingWithPerson):
def _do_something_with(self, the_person, request):
@ -119,7 +119,7 @@ class Follow(DoSomethingWithPerson):
except IntegrityError:
logger.info(' -- not creating a follow; it already exists')
class Unfollow(DoSomethingWithPerson):
class UnfollowUser(DoSomethingWithPerson):
def _do_something_with(self, the_person, request):
@ -142,76 +142,6 @@ class Unfollow(DoSomethingWithPerson):
logger.info(' -- not unfollowing; they weren\'t following '+\
'in the first place')
class UpdateCredentials(generics.GenericAPIView):
def patch(self, request, *args, **kwargs):
if request.user is None:
logger.debug(' -- user not logged in')
return error_response(401, 'Not logged in')
who = request.user.localperson
# The Mastodon spec doesn't say what to do
# if the user submits field names which don't
# exist!
unknown_fields = []
# FIXME: the data in "v" needs cleaning.
logger.info('-- updating user: %s', who)
for f,v in request.data.items():
logger.info(' -- setting %s = %s', f, v)
if f=='discoverable':
raise Http404("discoverable is not yet supported")
elif f=='bot':
who.bot = v
elif f=='display_name':
who.display_name = v
elif f=='note':
who.note = v
elif f=='avatar':
raise Http404("images are not yet supported")
elif f=='header':
raise Http404("images are not yet supported")
elif f=='locked':
who.locked = v
elif f=='source[privacy]':
who.default_visibility = v
elif f=='source[sensitive]':
who.default_sensitive = v
elif f=='source[language]':
who.language = v
elif f=='fields_attributes':
raise Http404("fields are not yet supported")
else:
logger.info(' -- field does not exist')
unknown_fields.append(f)
if unknown_fields:
logger.info(' -- aborting because of unknown fields')
raise Http404(f"some fields do not exist: {unknown_fields}")
who.save()
logger.info(' -- done.')
serializer = UserSerializerWithSource(
who,
context = {
'request': request,
},
)
return JsonResponse(
serializer.data,
status = 200,
reason = 'Done',
)
###########################
class VerifyCredentials(generics.GenericAPIView):

Wyświetl plik

@ -183,117 +183,6 @@ class Unreblog(DoSomethingWithStatus):
###########################
class DoSomethingWithPerson(generics.GenericAPIView):
serializer_class = UserSerializer
queryset = trilby_models.Person.objects.all()
def _do_something_with(self, the_person, request):
raise NotImplementedError()
def post(self, request, *args, **kwargs):
if request.user is None:
logger.debug(' -- user not logged in')
return error_response(401, 'Not logged in')
try:
the_person = get_object_or_404(
self.get_queryset(),
id = int(kwargs['user']),
)
except ValueError:
return error_response(404, 'Non-decimal ID')
result = self._do_something_with(the_person, request)
if result is None:
result = the_person
serializer = UserSerializer(
result,
context = {
'request': request,
},
)
return JsonResponse(
serializer.data,
status = 200,
reason = 'Done',
)
class Follow(DoSomethingWithPerson):
def _do_something_with(self, the_person, request):
try:
if the_person.auto_follow:
offer = None
else:
number = random.randint(0, 0xffffffff)
offer = uri_to_url(settings.KEPI['FOLLOW_REQUEST_LINK'] % {
'username': request.user.username,
'number': number,
})
follow = trilby_models.Follow(
follower = request.user.localperson,
following = the_person,
offer = offer,
)
with transaction.atomic():
follow.save(
send_signal = True,
)
logger.info(' -- follow: %s', follow)
logger.debug(' -- offer ID: %s', offer)
if the_person.auto_follow:
follow_back = trilby_models.Follow(
follower = the_person,
following = request.user.localperson,
offer = None,
)
with transaction.atomic():
follow_back.save(
send_signal = True,
)
logger.info(' -- follow back: %s', follow_back)
return the_person
except IntegrityError:
logger.info(' -- not creating a follow; it already exists')
class Unfollow(DoSomethingWithPerson):
def _do_something_with(self, the_person, request):
try:
follow = trilby_models.Follow.objects.get(
follower = request.user.localperson,
following = the_person,
)
logger.info(' -- unfollowing: %s', follow)
with transaction.atomic():
follow.delete(
send_signal = True,
)
return the_person
except trilby_models.Follow.DoesNotExist:
logger.info(' -- not unfollowing; they weren\'t following '+\
'in the first place')
class SpecificStatus(generics.GenericAPIView):
queryset = trilby_models.Status.objects.filter(remote_url=None)