kopia lustrzana https://gitlab.com/marnanel/chapeau
Status.content and Status.spoiler_text are now Status.content_source and Status.spoiler_source.
HTML renderings of each one are cached. You can access them at Status.content_as_html and Status.spoiler_as_html.merge-requests/2/head
rodzic
061ce40101
commit
37d53b2e4e
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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',
|
||||
)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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' 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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
]
|
|
@ -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:
|
||||
|
@ -280,7 +346,7 @@ class Status(PolymorphicModel):
|
|||
def __str__(self):
|
||||
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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
@ -154,7 +163,7 @@ def create_local_status(
|
|||
result = Status(
|
||||
remote_url = None,
|
||||
account = posted_by,
|
||||
content = content,
|
||||
content_source = content,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ class TimelineTestCase(TrilbyTestCase):
|
|||
def add_status(self, source, visibility, content):
|
||||
status = Status(
|
||||
account = source,
|
||||
content = content,
|
||||
content_source = content,
|
||||
visibility = visibility,
|
||||
)
|
||||
status.save()
|
||||
|
@ -39,12 +39,22 @@ class TimelineTestCase(TrilbyTestCase):
|
|||
as_user = None,
|
||||
):
|
||||
|
||||
details = sorted([x['content'] \
|
||||
for x in self.get(
|
||||
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:
|
||||
|
@ -54,10 +64,7 @@ class TimelineTestCase(TrilbyTestCase):
|
|||
|
||||
result += detail
|
||||
|
||||
logger.info("Timeline contents for %s as %s...",
|
||||
path,
|
||||
as_user)
|
||||
logger.info(" ...are %s",
|
||||
logger.info(" -- contents are %s",
|
||||
result)
|
||||
|
||||
return result
|
||||
|
@ -449,6 +456,49 @@ class TestHomeTimeline(TimelineTestCase):
|
|||
msg = 'default is 20',
|
||||
)
|
||||
|
||||
def temp_general_test_limit(self, count):
|
||||
# XXX temp
|
||||
|
||||
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()
|
||||
|
||||
for i in range(100):
|
||||
self.add_status(
|
||||
source=self.bob,
|
||||
content=str(i),
|
||||
visibility='A',
|
||||
)
|
||||
|
||||
self.add_status(
|
||||
source=self.carol,
|
||||
content=str(i),
|
||||
visibility='A',
|
||||
)
|
||||
|
||||
LOOP_COUNT = 50
|
||||
for j in range(LOOP_COUNT):
|
||||
logger.info("----------- Loop: %d of %d", j, LOOP_COUNT)
|
||||
for i in [count]:
|
||||
self.assertIsNotNone(
|
||||
self.timeline_contents(
|
||||
path = '/api/v1/timelines/home',
|
||||
data = {'limit': i},
|
||||
as_user = self.alice,
|
||||
),
|
||||
)
|
||||
|
||||
def xxx_test_1(self):
|
||||
self.temp_general_test_limit(1)
|
||||
|
||||
def xxx_test_100(self):
|
||||
self.temp_general_test_limit(100)
|
||||
|
||||
@httpretty.activate()
|
||||
def test_local(self):
|
||||
|
||||
|
|
|
@ -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]),
|
||||
|
|
Ładowanie…
Reference in New Issue