Add support for root parent for Comment entity

Added support for Diaspora `Comment` entity `thread_parent_guid`
attribute.

Added `root_target_id` and `root_target_guid` to `Comment` base entity.
This allows referring to a parent object up the hierarchy chain for
threaded comments.
merge-requests/149/head
Jason Robinson 2019-07-15 00:09:53 +03:00
rodzic 0b1231012d
commit 1e757ca43b
9 zmienionych plików z 87 dodań i 9 usunięć

Wyświetl plik

@ -22,6 +22,10 @@
ActivityPub profiles will parse these values from incoming profile documents. Diaspora entities will default to the inboxes in the specification. ActivityPub profiles will parse these values from incoming profile documents. Diaspora entities will default to the inboxes in the specification.
* Added support for Diaspora `Comment` entity `thread_parent_guid` attribute.
* Added `root_target_id` and `root_target_guid` to `Comment` base entity. This allows referring to a parent object up the hierarchy chain for threaded comments.
### Changed ### Changed
* **Backwards incompatible.** Lowest compatible Python version is now 3.6. * **Backwards incompatible.** Lowest compatible Python version is now 3.6.

Wyświetl plik

@ -5,7 +5,7 @@ from dirty_validators.basic import Email
from federation.entities.activitypub.enums import ActivityType from federation.entities.activitypub.enums import ActivityType
from federation.entities.mixins import ( from federation.entities.mixins import (
PublicMixin, TargetIDMixin, ParticipationMixin, CreatedAtMixin, RawContentMixin, OptionalRawContentMixin, PublicMixin, TargetIDMixin, ParticipationMixin, CreatedAtMixin, RawContentMixin, OptionalRawContentMixin,
EntityTypeMixin, ProviderDisplayNameMixin) EntityTypeMixin, ProviderDisplayNameMixin, RootTargetIDMixin)
class Accept(CreatedAtMixin, TargetIDMixin): class Accept(CreatedAtMixin, TargetIDMixin):
@ -34,7 +34,7 @@ class Image(PublicMixin, OptionalRawContentMixin, CreatedAtMixin):
self._required += ["remote_path", "remote_name"] self._required += ["remote_path", "remote_name"]
class Comment(RawContentMixin, ParticipationMixin, CreatedAtMixin): class Comment(RawContentMixin, ParticipationMixin, CreatedAtMixin, RootTargetIDMixin):
"""Represents a comment, linked to another object.""" """Represents a comment, linked to another object."""
participation = "comment" participation = "comment"
url = "" url = ""

Wyświetl plik

@ -15,7 +15,8 @@ class DiasporaComment(DiasporaRelayableMixin, Comment):
element = etree.Element(self._tag_name) element = etree.Element(self._tag_name)
struct_to_xml(element, [ struct_to_xml(element, [
{"guid": self.guid}, {"guid": self.guid},
{"parent_guid": self.target_guid}, {"parent_guid": self.root_target_guid or self.target_guid},
{"thread_parent_guid": self.target_guid},
{"author_signature": self.signature}, {"author_signature": self.signature},
{"parent_author_signature": self.parent_signature}, {"parent_author_signature": self.parent_signature},
{"text": self.raw_content}, {"text": self.raw_content},

Wyświetl plik

@ -188,6 +188,9 @@ def transform_attributes(attrs, cls):
elif key in ("target_guid", "root_guid", "parent_guid"): elif key in ("target_guid", "root_guid", "parent_guid"):
transformed["target_id"] = value transformed["target_id"] = value
transformed["target_guid"] = value transformed["target_guid"] = value
elif key == "thread_parent_guid":
transformed["root_target_id"] = value
transformed["root_target_guid"] = value
elif key in ("first_name", "last_name"): elif key in ("first_name", "last_name"):
values = [attrs.get('first_name'), attrs.get('last_name')] values = [attrs.get('first_name'), attrs.get('last_name')]
values = [v for v in values if v] values = [v for v in values if v]

Wyświetl plik

@ -137,6 +137,12 @@ class TargetIDMixin(BaseEntity):
self._required += ["target_id"] self._required += ["target_id"]
class RootTargetIDMixin(BaseEntity):
root_target_id = ""
root_target_handle = ""
root_target_guid = ""
class ParticipationMixin(TargetIDMixin): class ParticipationMixin(TargetIDMixin):
"""Reflects a participation to something.""" """Reflects a participation to something."""
participation = "" participation = ""

Wyświetl plik

@ -27,6 +27,19 @@ class TestEntitiesConvertToXML:
assert len(result.find("created_at").text) > 0 assert len(result.find("created_at").text) > 0
result.find("created_at").text = "" # timestamp makes testing painful result.find("created_at").text = "" # timestamp makes testing painful
converted = b"<comment><guid>guid</guid><parent_guid>target_guid</parent_guid>" \ converted = b"<comment><guid>guid</guid><parent_guid>target_guid</parent_guid>" \
b"<thread_parent_guid>target_guid</thread_parent_guid>" \
b"<author_signature>signature</author_signature><parent_author_signature>" \
b"</parent_author_signature><text>raw_content</text><author>alice@example.com</author>" \
b"<created_at></created_at></comment>"
assert etree.tostring(result) == converted
def test_nested_comment_to_xml(self, diasporanestedcomment):
result = diasporanestedcomment.to_xml()
assert result.tag == "comment"
assert len(result.find("created_at").text) > 0
result.find("created_at").text = "" # timestamp makes testing painful
converted = b"<comment><guid>guid</guid><parent_guid>target_guid</parent_guid>" \
b"<thread_parent_guid>thread_target_guid</thread_parent_guid>" \
b"<author_signature>signature</author_signature><parent_author_signature>" \ b"<author_signature>signature</author_signature><parent_author_signature>" \
b"</parent_author_signature><text>raw_content</text><author>alice@example.com</author>" \ b"</parent_author_signature><text>raw_content</text><author>alice@example.com</author>" \
b"<created_at></created_at></comment>" b"<created_at></created_at></comment>"
@ -122,13 +135,15 @@ class TestDiasporaRelayableMixin:
guid="guid", guid="guid",
target_id="target_guid", target_id="target_guid",
target_guid="target_guid", target_guid="target_guid",
root_target_id="target_guid",
root_target_guid="target_guid",
) )
entity.sign(get_dummy_private_key()) entity.sign(get_dummy_private_key())
assert entity.signature == "OWvW/Yxw4uCnx0WDn0n5/B4uhyZ8Pr6h3FZaw8J7PCXyPluOfYXFoHO21bykP8c2aVnuJNHe+lmeAkUC" \ assert entity.signature == "XZYggFdQHOicguZ0ReVJkYiK5othHgBgAtwnSmm4NR31qeLa76Ur/i2B5Xi9dtopDlNS8EbFy+MLJ1ds" \
"/kHnl4yxk/jqe3uroW842OWvsyDRQ11vHxhIqNMjiepFPkZmXX3vqrYYh5FrC/tUsZrEc8hHoOIHXFR2" \ "ovDjPsVC1nLZrL57y0v+HtwJas6hQqNbvmEyr1q6X+0p1i93eINzt/7bxcP5uEGxy8J4ItsJzbDVLlC5" \
"kGD0gPV+4EEG6pbMNNZ+SBVun0hvruX8iKQVnBdc/+zUI9+T/MZmLyqTq/CvuPxDyHzQPSHi68N9rJyr" \ "3ZtIg7pmhR0ltqNqBHrgL8WDokfGKFlXqANchbD+Xeyv2COGbI78LwplVdYjHW1+jefjpYhMCxayIvMv" \
"4Xa1K+R33Xq8eHHxs8LVNRqzaHGeD3DX8yBu/vP9TYmZsiWlymbuGwLCa4Yfv/VS1hQZovhg6YTxV4CR" \ "WS8TV1hMTqUz+zSqoCHU04RgjjGW8e8vINDblQwMfEMeJ5T6OP5RiU3zCqDc3uL2zxHHh9IGC+clVuhP" \
"v4ToGL+CAJ7UHEugRRBwDw==" "HTv8tHUHNLgc2vIzRtGh6w=="
def test_signing_like_works(self): def test_signing_like_works(self):
entity = DiasporaLike( entity = DiasporaLike(

Wyświetl plik

@ -18,7 +18,7 @@ from federation.tests.fixtures.payloads import (
DIASPORA_POST_WITH_PHOTOS, DIASPORA_CONTACT, DIASPORA_POST_WITH_PHOTOS, DIASPORA_CONTACT,
DIASPORA_PROFILE_EMPTY_TAGS, DIASPORA_RESHARE, DIASPORA_PROFILE_EMPTY_TAGS, DIASPORA_RESHARE,
DIASPORA_RESHARE_WITH_EXTRA_PROPERTIES, DIASPORA_POST_SIMPLE_WITH_MENTION, DIASPORA_RESHARE_WITH_EXTRA_PROPERTIES, DIASPORA_POST_SIMPLE_WITH_MENTION,
DIASPORA_PROFILE_FIRST_NAME_ONLY) DIASPORA_PROFILE_FIRST_NAME_ONLY, DIASPORA_POST_COMMENT_NESTED)
class TestDiasporaEntityMappersReceive: class TestDiasporaEntityMappersReceive:
@ -71,6 +71,7 @@ class TestDiasporaEntityMappersReceive:
assert isinstance(comment, DiasporaComment) assert isinstance(comment, DiasporaComment)
assert isinstance(comment, Comment) assert isinstance(comment, Comment)
assert comment.target_guid == "((parent_guidparent_guidparent_guidparent_guid))" assert comment.target_guid == "((parent_guidparent_guidparent_guidparent_guid))"
assert comment.root_target_guid == ""
assert comment.guid == "((guidguidguidguidguidguid))" assert comment.guid == "((guidguidguidguidguidguid))"
assert comment.handle == "alice@alice.diaspora.example.org" assert comment.handle == "alice@alice.diaspora.example.org"
assert comment.participation == "comment" assert comment.participation == "comment"
@ -81,6 +82,26 @@ class TestDiasporaEntityMappersReceive:
] ]
mock_validate.assert_called_once_with() mock_validate.assert_called_once_with()
@patch("federation.entities.diaspora.mappers.DiasporaComment._validate_signatures")
def test_message_to_objects_nested_comment(self, mock_validate):
entities = message_to_objects(DIASPORA_POST_COMMENT_NESTED, "alice@alice.diaspora.example.org",
sender_key_fetcher=Mock())
assert len(entities) == 1
comment = entities[0]
assert isinstance(comment, DiasporaComment)
assert isinstance(comment, Comment)
assert comment.target_guid == "((parent_guidparent_guidparent_guidparent_guid))"
assert comment.root_target_guid == "((threadparentguid))"
assert comment.guid == "((guidguidguidguidguidguid))"
assert comment.handle == "alice@alice.diaspora.example.org"
assert comment.participation == "comment"
assert comment.raw_content == "((text))"
assert comment.signature == "((signature))"
assert comment._xml_tags == [
"guid", "parent_guid", "thread_parent_guid", "text", "author",
]
mock_validate.assert_called_once_with()
@patch("federation.entities.diaspora.mappers.DiasporaLike._validate_signatures") @patch("federation.entities.diaspora.mappers.DiasporaLike._validate_signatures")
def test_message_to_objects_like(self, mock_validate): def test_message_to_objects_like(self, mock_validate):
entities = message_to_objects( entities = message_to_objects(

Wyświetl plik

@ -118,6 +118,22 @@ def diasporacomment():
) )
@pytest.fixture
def diasporanestedcomment():
return DiasporaComment(
raw_content="raw_content",
signature="signature",
id="guid",
guid="guid",
actor_id="alice@example.com",
handle="alice@example.com",
target_id="thread_target_guid",
target_guid="thread_target_guid",
root_target_id="target_guid",
root_target_guid="target_guid",
)
@pytest.fixture @pytest.fixture
def diasporacontact(): def diasporacontact():
return DiasporaContact( return DiasporaContact(

Wyświetl plik

@ -91,6 +91,18 @@ DIASPORA_POST_COMMENT = """
</comment> </comment>
""" """
DIASPORA_POST_COMMENT_NESTED = """
<comment>
<guid>((guidguidguidguidguidguid))</guid>
<parent_guid>((parent_guidparent_guidparent_guidparent_guid))</parent_guid>
<thread_parent_guid>((threadparentguid))</thread_parent_guid>
<author_signature>((base64-encoded data))</author_signature>
<text>((text))</text>
<author>alice@alice.diaspora.example.org</author>
<author_signature>((signature))</author_signature>
</comment>
"""
DIASPORA_POST_LIKE = """ DIASPORA_POST_LIKE = """
<like> <like>
<parent_type>Post</parent_type> <parent_type>Post</parent_type>