Merge branch 'issue-68' into 'main'

Issue 68

See merge request marnanel/chapeau!2
2021-05-layout
Marnanel Thurman 2021-02-18 20:26:57 +00:00
commit 6174859722
16 zmienionych plików z 747 dodań i 179 usunięć

Wyświetl plik

@ -231,9 +231,9 @@ def on_note(fields, address):
remote_url = fields['id'],
account = poster,
in_reply_to = in_reply_to,
content = fields['content'],
content_source = fields['content'],
sensitive = is_sensitive,
spoiler_text = spoiler_text,
spoiler_source = spoiler_text,
visibility = visibility,
language = language,
)

Wyświetl plik

@ -33,7 +33,7 @@ class StatusObjectSerializer(serializers.ModelSerializer):
'id': status.url,
'url': status.url,
'type': 'Note',
'summary': status.spoiler_text_as_html,
'summary': status.spoiler_as_html,
'inReplyTo': status.in_reply_to,
'published': status.created_at,
'attributedTo': status.account.url,
@ -43,7 +43,7 @@ class StatusObjectSerializer(serializers.ModelSerializer):
'conversation': status.conversation,
'content': status.content_as_html,
'contentMap': {
status.language: status.content,
status.language: status.content_source,
},
'attachment': status.media_attachments,
'tag': status.tags,

Wyświetl plik

@ -125,7 +125,7 @@ class Tests(TestCase):
)
self.assertEqual(
original_status.content,
original_status.content_source,
'Hello world',
msg = 'the status was reblogged at the end',
)

Wyświetl plik

@ -69,7 +69,7 @@ class Tests(Create_TestCase):
import kepi.trilby_api.models as trilby_models
result = trilby_models.Status.objects.filter(
content = content,
content_source = content,
)
if result:

Wyświetl plik

@ -82,8 +82,22 @@ class Tests(TestCase):
result = trilby_models.Status(
account = self._alice,
visibility = trilby_utils.VISIBILITY_PUBLIC,
content = "<p>Victoria Wood parodying Peter Skellern. I laughed so much at this, though you might have to know both singers&apos; work in order to find it quite as funny.</p><p>- love song<br />- self-doubt<br />- refs to northern England<br />- preamble<br />- piano solo<br />- brass band<br />- choir backing<br />- love is cosy<br />- heavy rhotic vowels</p><p><a href=\"https://youtu.be/782hqdmnq7g\" rel=\"nofollow noopener\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">youtu.be/782hqdmnq7g</span><span class=\"invisible\"></span></a></p>",
)
content_source = """Victoria Wood parodying Peter Skellern.
I laughed so much at this, though you might have to know both singers' work
in order to find it quite as funny:
- love song
- self-doubt
- refs to northern England
- preamble
- piano solo
- brass band
- choir backing
- love is cosy
- heavy rhotic vowels
https://youtu.be/782hqdmnq7g""",
)
result.save()
return result

Wyświetl plik

@ -59,7 +59,7 @@ def on_posted(sender, **kwargs):
"object": {
"type": "Note",
"id": sender.url,
"content": sender.content,
"content": sender.content_as_html,
}
},
sender = sender.account,

Wyświetl plik

@ -0,0 +1,38 @@
# Generated by Django 3.0.9 on 2021-02-16 19:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('trilby_api', '0028_mention'),
]
operations = [
migrations.RenameField(
model_name='status',
old_name='spoiler_text',
new_name='spoiler_source',
),
migrations.RemoveField(
model_name='status',
name='content',
),
migrations.AddField(
model_name='status',
name='content_as_html_denormed',
field=models.TextField(default=None, editable=False, help_text='HTML rendering of content_source. Do not edit!', null=True),
),
migrations.AddField(
model_name='status',
name='content_source',
field=models.TextField(default='', help_text='Text of the status, as entered'),
preserve_default=False,
),
migrations.AddField(
model_name='status',
name='spoiler_as_html_denormed',
field=models.CharField(default=None, editable=False, max_length=255, null=True),
),
]

Wyświetl plik

@ -9,6 +9,7 @@ logger = logging.getLogger(name='kepi')
from polymorphic.models import PolymorphicModel
from django.db import models
from django.db.models import Q
from django.db.models.constraints import UniqueConstraint
from django.contrib.auth.models import AbstractUser
from django.conf import settings
@ -591,41 +592,52 @@ class LocalPerson(Person):
import kepi.trilby_api.models as trilby_models
# tags aren't implemented; FIXME
everything_youre_tagged_in = trilby_models.Status.objects.none()
# "Everything you're tagged in":
# tags aren't implemented; FIXME
logger.debug("%s.inbox: tagged in: %s",
self, everything_youre_tagged_in)
all_your_posts = Q(account = self)
all_your_posts = trilby_models.Status.objects.filter(
account = self,
)
# note: querysets don't get evaluated unless used,
# so the debug logging doesn't cause a db hit
# unless it's actually turned on.
logger.debug("%s.inbox: all your posts: %s",
self, all_your_posts)
logger.debug("%s.inbox: your own posts: %s",
self,
trilby_models.Status.objects.filter(
all_your_posts
))
all_your_friends_public_posts = trilby_models.Status.objects.filter(
all_your_friends_public_posts = Q(
visibility = trilby_utils.VISIBILITY_PUBLIC,
account__rel_following__following = self,
account__rel_followers__follower = self,
)
logger.debug("%s.inbox: all friends' public: %s",
self, all_your_friends_public_posts)
logger.debug("%s.inbox: your friends' public posts: %s",
self,
trilby_models.Status.objects.filter(
all_your_friends_public_posts
))
all_your_mutuals_private_posts = trilby_models.Status.objects.filter(
all_your_mutuals_private_posts = Q(
visibility = trilby_utils.VISIBILITY_PRIVATE,
account__rel_following__following = self,
account__rel_followers__follower = self,
)
logger.debug("%s.inbox: all mutuals' private: %s",
self, all_your_mutuals_private_posts)
logger.debug("%s.inbox: your mutuals' private posts: %s",
self,
trilby_models.Status.objects.filter(
all_your_mutuals_private_posts
))
result = everything_youre_tagged_in.union(
all_your_posts,
all_your_friends_public_posts,
all_your_mutuals_private_posts,
)
result = trilby_models.Status.objects.filter(
all_your_posts | \
all_your_friends_public_posts | \
all_your_mutuals_private_posts
)
logger.info("%s.inbox: contains %s",
self, result)
return result

Wyświetl plik

@ -59,7 +59,15 @@ class Status(PolymorphicModel):
blank = True,
)
content = models.TextField(
content_source = models.TextField(
help_text = 'Text of the status, as entered',
)
content_as_html_denormed = models.TextField(
help_text = 'HTML rendering of content_source. Do not edit!',
editable = False,
null = True,
default = None,
)
created_at = models.DateTimeField(
@ -72,13 +80,20 @@ class Status(PolymorphicModel):
default = False,
)
spoiler_text = models.CharField(
spoiler_source = models.CharField(
max_length = 255,
null = True,
blank = True,
default = '',
)
spoiler_as_html_denormed = models.CharField(
max_length = 255,
null = True,
editable = False,
default = None,
)
visibility = models.CharField(
max_length = 1,
default = trilby_utils.VISIBILITY_PUBLIC,
@ -107,6 +122,44 @@ class Status(PolymorphicModel):
default = None,
)
@property
def content_as_html(self):
"""
Returns an HTML rendition of content_source.
The return value will be cached.
Saving the record will clear this cache.
"""
if self.content_as_html_denormed is not None:
return self.content_as_html_denormed
if self.content_source is None:
result = '<p></p>'
else:
result = markdown.markdown(self.content_source)
self.content_as_html_denormed = result
return result
@property
def spoiler_as_html(self):
"""
Returns an HTML rendition of spoiler_source.
The return value will be cached.
Saving the record will clear this cache.
"""
if self.spoiler_as_html_denormed is not None:
return self.spoiler_as_html_denormed
if self.spoiler_source is None:
result = '<p></p>'
else:
result = markdown.markdown(self.spoiler_source)
self.spoiler_as_html_denormed = result
return result
@property
def emojis(self):
return [] # TODO
@ -268,6 +321,19 @@ class Status(PolymorphicModel):
if self.in_reply_to == self:
raise ValueError("Status can't be a reply to itself")
if not newly_made:
old = self.__class__.objects.get(pk=self.pk)
if self.content_source != old.content_source:
logger.debug("%s: content changed; flushing HTML cache",
self)
self.content_as_html_denormed = None
if self.spoiler_source != old.spoiler_source:
logger.debug("%s: spoiler changed; flushing HTML cache",
self)
self.spoiler_as_html_denormed = None
super().save(*args, **kwargs)
if send_signal and newly_made:
@ -278,9 +344,9 @@ class Status(PolymorphicModel):
trilby_signals.reblogged.send(sender=self)
def __str__(self):
return '[Status %s: %s]' % (
return '%s: %s' % (
self.id,
self.content,
self.content_source,
)
@classmethod
@ -350,20 +416,8 @@ class Status(PolymorphicModel):
# HTML and one is plain text. But the docs don't
# seem to be forthcoming on this point, so we'll
# just have to wait until we find out.
return self.content
return self.content_source
@property
def is_local(self):
return self.remote_url is None
@property
def content_as_html(self):
if not self.content:
return '<p></p>'
return markdown.markdown(self.content)
@property
def spoiler_text_as_html(self):
if not self.spoiler_text:
return '<p></p>'
return markdown.markdown(self.spoiler_text)

Wyświetl plik

@ -208,26 +208,24 @@ class StatusSerializer(serializers.ModelSerializer):
# "content" is read-only for HTML;
# "status" is write-only for text (or Markdown)
content = serializers.SerializerMethodField(
content = serializers.CharField(
source='content_as_html',
read_only = True)
status = serializers.CharField(
source='source_text',
source='content_source',
write_only = True)
def get_content(self, status):
result = markdown.markdown(status.content)
return result
created_at = serializers.DateTimeField(
required = False,
read_only = True)
# TODO Media
# TODO Media
sensitive = serializers.BooleanField(
required = False)
spoiler_text = serializers.CharField(
source='spoiler_source',
allow_blank = True,
required = False)

Wyświetl plik

@ -1,9 +1,18 @@
# trilby_api/tests/__init__.py
#
# Part of kepi.
# Copyright (c) 2018-2021 Marnanel Thurman.
# Licensed under the GNU Public License v2.
from django.test import TestCase, Client
from rest_framework.test import force_authenticate, APIClient
from kepi.trilby_api.models import *
from django.conf import settings
import json
import logging
logger = logging.getLogger(name='kepi')
ACCOUNT_EXPECTED = {
'username': 'alice',
'acct': 'alice',
@ -74,11 +83,11 @@ class TrilbyTestCase(TestCase):
return result
def request(self, verb, path,
data={},
data = None,
as_user=None,
expect_result=200,
parse_result=True,
*args, **kwargs,
**extra,
):
c = APIClient()
@ -92,8 +101,7 @@ class TrilbyTestCase(TestCase):
path=path,
data=data,
format='json',
*args,
**kwargs,
extra=extra,
)
if expect_result is not None:
@ -155,7 +163,7 @@ def create_local_status(
result = Status(
remote_url = None,
account = posted_by,
content = content,
content_source = content,
**kwargs,
)

Wyświetl plik

@ -23,7 +23,7 @@ class TestReblog(TestCase):
)
reblog = create_local_status(
content = original.content,
content = original.content_source,
posted_by = bob,
reblog_of = original,
)
@ -48,7 +48,7 @@ class TestReblog(TestCase):
)
reblog = create_local_status(
content = original.content,
content = original.content_source,
posted_by = bob,
reblog_of = original,
)
@ -87,7 +87,7 @@ class TestReblog(TestCase):
for i in range(1, 20):
reblog = create_local_status(
content = original.content,
content = original.content_source,
posted_by = bob,
reblog_of = original,
)

Wyświetl plik

@ -685,7 +685,7 @@ class TestGetStatus(TrilbyTestCase):
remote_url = "https://example.org/people/bob/status/100",
account = self._bob,
in_reply_to = self._alice_status,
content = "Buttercups our gold.",
content_source = "Buttercups our gold.",
)
self._bob_status.save()

Wyświetl plik

@ -1,7 +1,7 @@
# test_timelines.py
#
# Part of kepi.
# Copyright (c) 2018-2020 Marnanel Thurman.
# Copyright (c) 2018-2021 Marnanel Thurman.
# Licensed under the GNU Public License v2.
import logging
@ -11,123 +11,515 @@ from rest_framework.test import APIClient, force_authenticate
from kepi.trilby_api.views import *
from kepi.trilby_api.tests import *
from kepi.trilby_api.models import *
from kepi.bowler_pub.tests import create_remote_person
from django.conf import settings
from unittest import skip
import httpretty
# Tests for timelines. API docs are here:
# https://docs.joinmastodon.org/methods/statuses/
"""
Tests for timelines. API docs are here:
https://docs.joinmastodon.org/methods/timelines/
"""
TIMELINE_DATA = [
# Visibility is:
# A=public: visible to anyone, and in public timelines
# U=unlisted: visible to anyone, but not in public timelines
# X=private: visible to followers and anyone tagged
# D=direct: visible only to those who are tagged
class TimelineTestCase(TrilbyTestCase):
# We haven't yet implemented:
# - (user) tags
# - hashtags
# - user lists
# - following users but hiding reblogs
# and when we do, these tests will need updating.
#
# All statuses are posted by alice.
#
# id visibility visible in
( 'A', 'A',
['public', 'follower', 'stranger', 'home', ], ),
( 'B', 'U',
['follower', 'stranger', 'home', ], ),
( 'C', 'X',
['follower', 'home',], ),
( 'D', 'D',
['home', ], ),
def add_status(self, source, visibility, content,
remote_url = None):
status = Status(
account = source,
content_source = content,
visibility = visibility,
remote_url = remote_url,
)
status.save()
]
logger.info("Created status: %s", status)
class TestTimelines(TrilbyTestCase):
return status
def _set_up(self):
self._alice = create_local_person("alice")
for (id, visibility, visible_in) in TIMELINE_DATA:
status = Status(
account = self._alice,
content = id,
visibility = visibility,
)
status.save()
def _check_timelines(self,
situation,
def timeline_contents(self,
path,
as_user):
expected = []
for (id, visibility, visible_in) in TIMELINE_DATA:
if situation in visible_in:
expected.append(f'<p>{id}</p>')
expected = sorted(expected)
details = sorted([x['content'] \
for x in self.get(
path = path,
as_user = as_user,
)])
self.assertListEqual(
expected,
details,
msg = f"Visibility in '{situation}' mismatch: "+\
f"expected {expected}, but got {details}.",
)
def test_public(self):
self._set_up()
self._check_timelines(
situation = 'public',
path = '/api/v1/timelines/public',
data = None,
as_user = None,
):
logger.info("Timeline contents for %s as %s...",
path,
as_user)
found = self.get(
path = path,
data = data,
as_user = as_user,
)
logger.info(" -- retrieved")
details = sorted([x['content'] for x in found])
logger.debug(" -- sorted as %s",
details)
result = ''
for detail in details:
if detail.startswith('<p>') and detail.endswith('</p>'):
detail = detail[3:-4]
result += detail
logger.info(" -- contents are %s",
result)
return result
class TestPublicTimeline(TimelineTestCase):
def test_as_anon(self):
alice = create_local_person("alice")
self.add_status(source=alice, content='A', visibility='A')
self.add_status(source=alice, content='B', visibility='U')
self.add_status(source=alice, content='C', visibility='X')
self.add_status(source=alice, content='D', visibility='D')
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/public',
as_user = None,
),
'A',
)
def test_follower(self):
self._set_up()
self._george = create_local_person("george")
def test_as_user(self):
alice = create_local_person("alice")
self.add_status(source=alice, content='A', visibility='A')
self.add_status(source=alice, content='B', visibility='U')
self.add_status(source=alice, content='C', visibility='X')
self.add_status(source=alice, content='D', visibility='D')
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/public',
as_user = alice,
),
'A',
)
def test_as_stranger(self):
alice = create_local_person("alice")
henry = create_local_person("henry")
self.add_status(source=alice, content='A', visibility='A')
self.add_status(source=alice, content='B', visibility='U')
self.add_status(source=alice, content='C', visibility='X')
self.add_status(source=alice, content='D', visibility='D')
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/public',
as_user = henry,
),
'A',
)
@httpretty.activate()
def test_local_and_remote(self):
alice = create_local_person("alice")
peter = create_remote_person(
remote_url = "https://example.com/users/peter",
name = "peter",
auto_fetch = True,
)
self.add_status(source=alice, content='A', visibility='A')
self.add_status(source=peter, content='B', visibility='A',
remote_url = 'https://example.com/users/peter/B')
self.add_status(source=alice, content='C', visibility='A')
self.add_status(source=peter, content='D', visibility='A',
remote_url = 'https://example.com/users/peter/D')
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/public',
),
'ABCD',
)
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/public',
data = {'local': 'true'},
),
'AC',
)
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/public',
data = {'local': 'false'},
),
'ABCD',
)
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/public',
data = {'remote': 'true'},
),
'BD',
)
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/public',
data = {'remote': 'false'},
),
'ABCD',
)
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/public',
data = {'local': 'true', 'remote': 'true'},
),
'',
)
def test_only_media(self):
# We don't support added media at present anyway,
# so turning this on will always get the empty set
alice = create_local_person("alice")
self.add_status(source=alice, content='A', visibility='A')
self.add_status(source=alice, content='B', visibility='A')
self.add_status(source=alice, content='C', visibility='A')
self.add_status(source=alice, content='D', visibility='A')
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/public',
data = {'only_media': 'true'},
),
'',
)
def test_max_since_and_min(self):
alice = create_local_person("alice")
self.add_status(source=alice, content='A', visibility='A')
self.add_status(source=alice, content='B', visibility='A')
status_c = self.add_status(source=alice, content='C', visibility='A')
self.add_status(source=alice, content='D', visibility='A')
c_id = str(status_c.id)
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/public',
data = {'since_id': status_c.id},
),
'D',
)
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/public',
data = {'max_id': status_c.id},
),
'ABC',
)
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/public',
data = {'min_id': status_c.id},
),
'CD',
)
def test_limit(self):
alice = create_local_person("alice")
alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
for i in range(len(alphabet)):
self.add_status(
source=alice,
content=alphabet[i],
visibility='A',
)
for i in range(1, len(alphabet)):
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/public',
data = {'limit': i},
),
alphabet[:i],
)
# the default is specified as 20
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/public',
),
alphabet[:20],
msg = 'default is 20',
)
class TestHomeTimeline(TimelineTestCase):
def add_standard_statuses(self):
self.alice = create_local_person("alice")
self.bob = create_local_person("bob")
self.carol = create_local_person("carol")
self.add_status(source=self.bob, content='A', visibility='A')
self.add_status(source=self.carol, content='B', visibility='A')
self.add_status(source=self.carol, content='C', visibility='A')
self.add_status(source=self.bob, content='D', visibility='A')
Follow(
follower=self.alice,
following=self.bob,
offer=None).save()
def follow_carol(self):
Follow(
follower=self.alice,
following=self.carol,
offer=None).save()
def test_not_anon(self):
found = self.get(
path = '/api/v1/timelines/home',
as_user = None,
expect_result = 401,
)
def test_0_simple(self):
self.add_standard_statuses()
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/home',
as_user = self.alice,
),
'AD',
)
self.follow_carol()
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/home',
as_user = self.alice,
),
'ABCD',
)
def test_max_since_and_min(self):
self.add_standard_statuses()
c_id = '3' # FIXME hack
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/home',
data = {'since_id': c_id},
as_user = self.alice,
),
'D',
)
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/home',
data = {'max_id': c_id},
as_user = self.alice,
),
'A',
)
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/home',
data = {'min_id': c_id},
as_user = self.alice,
),
'D',
)
self.follow_carol()
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/home',
data = {'since_id': c_id},
as_user = self.alice,
),
'D',
)
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/home',
data = {'max_id': c_id},
as_user = self.alice,
),
'ABC',
)
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/home',
data = {'min_id': c_id},
as_user = self.alice,
),
'CD',
)
def test_limit(self):
self.alice = create_local_person("alice")
self.bob = create_local_person("bob")
self.carol = create_local_person("carol")
Follow(
follower=self.alice,
following=self.bob,
offer=None).save()
alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
for i in range(len(alphabet)):
self.add_status(
source=self.bob,
content=alphabet[i],
visibility='A',
)
self.add_status(
source=self.carol,
content=alphabet[i].lower(),
visibility='A',
)
for i in range(1, len(alphabet)):
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/home',
data = {'limit': i},
as_user = self.alice,
),
alphabet[:i],
)
# the default is specified as 20
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/home',
as_user = self.alice,
),
alphabet[:20],
msg = 'default is 20',
)
@httpretty.activate()
def test_local(self):
self.add_standard_statuses()
self.peter = create_remote_person(
remote_url = "https://example.com/users/peter",
name = "peter",
auto_fetch = True,
)
for letter in 'PQ':
self.add_status(source=self.peter,
remote_url = 'https://example.com/users/peter/{}'.format(
letter,
),
content=letter,
visibility='A')
Follow(
follower = self.alice,
following = self.peter,
offer = None,
).save()
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/home',
as_user = self.alice,
),
'ADPQ',
)
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/home',
data = {'local': 'true'},
as_user = self.alice,
),
'AD',
)
def test_as_follower(self):
alice = create_local_person("alice")
george = create_local_person("george")
follow = Follow(
follower = self._george,
following = self._alice,
follower = george,
following = alice,
offer = None,
)
follow.save()
self._check_timelines(
situation = 'public',
path = '/api/v1/timelines/public',
as_user = self._george,
self.add_status(source=alice, content='A', visibility='A')
self.add_status(source=alice, content='B', visibility='U')
self.add_status(source=alice, content='C', visibility='X')
self.add_status(source=alice, content='D', visibility='D')
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/home',
as_user = george,
),
'A',
)
def test_stranger(self):
self._set_up()
self._henry = create_local_person("henry")
follow = Follow(
follower = alice,
following = george,
offer = None,
)
follow.save() # they are now mutuals
self._check_timelines(
situation = 'public',
path = '/api/v1/timelines/public',
as_user = self._henry,
)
def test_home(self):
self._set_up()
self._check_timelines(
situation = 'home',
path = '/api/v1/timelines/home',
as_user = self._alice,
self.assertEqual(
self.timeline_contents(
path = '/api/v1/timelines/home',
as_user = george,
),
'AC',
)
class TestTimelinesNotImplemented(TimelineTestCase):
@skip("to be implemented later")
def test_hashtag(self):
raise NotImplementedError()

Wyświetl plik

@ -135,18 +135,18 @@ class Reblog(DoSomethingWithStatus):
# https://github.com/tootsuite/mastodon/issues/13479
# For now, I'm assuming that you can.
content = 'RT {}'.format(the_status.content)
content_source = 'RT {}'.format(the_status.content_source)
new_status = trilby_models.Status(
# Fields which are different in a reblog:
account = request.user.localperson,
content = content,
content_source = content_source,
reblog_of = the_status,
# Fields which are just copied in:
sensitive = the_status.sensitive,
spoiler_text = the_status.spoiler_text,
spoiler_source = the_status.spoiler_source,
visibility = the_status.visibility,
language = the_status.language,
in_reply_to = the_status.in_reply_to,
@ -397,9 +397,9 @@ class Statuses(generics.ListCreateAPIView,
status = trilby_models.Status(
account = request.user.localperson,
content = data.get('status', ''),
content_source = data.get('status', ''),
sensitive = data.get('sensitive', False),
spoiler_text = data.get('spoiler_text', ''),
spoiler_source = data.get('spoiler_text', ''),
visibility = data.get('visibility', 'public'),
language = data.get('language',
settings.KEPI['LANGUAGES'][0]),

Wyświetl plik

@ -31,6 +31,7 @@ import json
import re
import random
DEFAULT_TIMELINE_SLICE_LENGTH = 20
class AbstractTimeline(generics.ListAPIView):
@ -39,29 +40,82 @@ class AbstractTimeline(generics.ListAPIView):
IsAuthenticated,
]
def get_queryset(self, request):
def get_queryset(self):
raise NotImplementedError("cannot query abstract timeline")
def get(self, request):
queryset = self.get_queryset(request)
serializer = self.serializer_class(queryset,
many = True,
context = {
'request': request,
})
return Response(serializer.data)
def filter_queryset(self, queryset,
min_id = None,
max_id = None,
since_id = None,
local = False,
remote = False,
limit = DEFAULT_TIMELINE_SLICE_LENGTH,
*args, **kwargs,
):
PUBLIC_TIMELINE_SLICE_LENGTH = 20
logger.debug("Timeline queryset: %s", queryset)
if 'min_id' in self.request.query_params:
queryset = queryset.filter(
id__gte = int(self.request.query_params['min_id']),
)
logger.debug(" -- after min_id: %s", queryset)
if 'max_id' in self.request.query_params:
queryset = queryset.filter(
id__lte = int(self.request.query_params['max_id']),
)
logger.debug(" -- after max_id: %s", queryset)
if 'since_id' in self.request.query_params:
queryset = queryset.filter(
id__gt = int(self.request.query_params['since_id']),
)
logger.debug(" -- after since_id: %s", queryset)
if self.request.query_params.get('local', '')=='true':
queryset = queryset.filter(
remote_url__isnull = True,
)
logger.debug(" -- after local: %s", queryset)
if self.request.query_params.get('remote', '')=='true':
queryset = queryset.filter(
remote_url__isnull = False,
)
logger.debug(" -- after remote: %s", queryset)
if 'only_media' in self.request.query_params:
# We don't support media at present, so this will give us
# the empty set
queryset = queryset.none()
logger.debug(" -- after only_media: %s", queryset)
# Slicing the queryset must be done last,
# since running operations on a sliced queryset
# causes evaluation.
limit = int(self.request.query_params.get('limit',
default = DEFAULT_TIMELINE_SLICE_LENGTH,
))
queryset = queryset[:limit]
logger.debug(" -- after slice of %d: %s",
limit,
queryset,
)
return queryset
class PublicTimeline(AbstractTimeline):
permission_classes = ()
def get_queryset(self, request):
def get_queryset(self):
result = trilby_models.Status.objects.filter(
visibility = trilby_utils.VISIBILITY_PUBLIC,
)[:PUBLIC_TIMELINE_SLICE_LENGTH]
)
return result
@ -71,9 +125,9 @@ class HomeTimeline(AbstractTimeline):
IsAuthenticated,
]
def get_queryset(self, request):
def get_queryset(self):
result = request.user.localperson.inbox
result = self.request.user.localperson.inbox
logger.debug("Home timeline is %s",
result)
@ -82,8 +136,6 @@ class HomeTimeline(AbstractTimeline):
########################################
########################################
class UserFeed(View):
permission_classes = ()