jenkins-bot has submitted this change and it was merged.
Change subject: Reply to existing posts and topics in Flow ......................................................................
Reply to existing posts and topics in Flow
This patch adds code to reply to topics, via their root posts, and ordinary posts. It includes tests to post replies to topics and replies to those replies. flow_edit_tests.py, a new test module, was created in this change for Flow-related edit tests.
Bug: T105439 Change-Id: I96b123f98da847d1e9fcb87558fdb2ebde1a04cf --- M pywikibot/flow.py M pywikibot/site.py A tests/flow_edit_tests.py 3 files changed, 205 insertions(+), 14 deletions(-)
Approvals: John Vandenberg: Looks good to me, approved jenkins-bot: Verified
diff --git a/pywikibot/flow.py b/pywikibot/flow.py index c9fe320..9c032b4 100644 --- a/pywikibot/flow.py +++ b/pywikibot/flow.py @@ -81,9 +81,9 @@
"""A Flow discussion board."""
- def _load(self): + def _load(self, force=False): """Load and cache the Board's data, derived from its topic list.""" - if not hasattr(self, '_data'): + if not hasattr(self, '_data') or force: self._data = self.site.load_board(self) return self._data
@@ -143,10 +143,10 @@
"""A Flow discussion topic."""
- def _load(self): + def _load(self, format='wikitext', force=False): """Load and cache the Topic's data.""" - if not hasattr(self, '_data'): - self._data = self.site.load_topic(self) + if not hasattr(self, '_data') or force: + self._data = self.site.load_topic(self, format) return self._data
@classmethod @@ -192,6 +192,18 @@ @rtype: list of Posts """ return self.root.replies(format=format, force=force) + + def reply(self, content, format='wikitext'): + """A convenience method to reply to this topic's root post. + + @param content: The content of the new post + @type content: unicode + @param format: The format of the given content + @type format: str ('wikitext' or 'html') + @return: The new reply to this topic's root post + @rtype: Post + """ + return self.root.reply(content, format)
# Flow non-page-like objects @@ -258,9 +270,7 @@ if self.uuid not in data['posts']: raise ValueError('Post not found in supplied data.')
- self._data = data current_revision_id = data['posts'][self.uuid][0] - if current_revision_id not in data['revisions']: raise ValueError('Current revision of post' 'not found in supplied data.') @@ -272,12 +282,15 @@ assert isinstance(content['content'], unicode) self._content[content['format']] = content['content']
- def _load(self, format='wikitext'): + def _load(self, format='wikitext', load_from_topic=False): """Load and cache the Post's data using the given content format.""" - data = self.site.load_post_current_revision(self.page, self.uuid, - format) + if load_from_topic: + data = self.page._load(format=format, force=True) + else: + data = self.site.load_post_current_revision(self.page, self.uuid, + format) self._set_data(data) - return self._data + return self._current_revision
@property def uuid(self): @@ -342,10 +355,34 @@ if hasattr(self, '_replies') and not force: return self._replies
+ # load_from_topic workaround due to T106733 + # (replies not returned by view-post) if not hasattr(self, '_current_revision') or force: - self._load(format) + self._load(format, load_from_topic=True)
reply_uuids = self._current_revision['replies'] self._replies = [Post(self.page, uuid) for uuid in reply_uuids]
return self._replies + + def reply(self, content, format='wikitext'): + """Reply to this post. + + @param content: The content of the new post + @type content: unicode + @param format: The format of the given content + @type format: str ('wikitext' or 'html') + @return: The new reply post + @rtype: Post + """ + self._load() + reply_url = self._current_revision['actions']['reply']['url'] + parsed_url = urlparse(reply_url) + params = parse_qs(parsed_url.query) + reply_to = params['topic_postId'] + if self.uuid == reply_to: + del self._current_revision + del self._replies + data = self.site.reply_to_post(self.page, reply_to, content, format) + post = Post(self.page, data['post-id']) + return post diff --git a/pywikibot/site.py b/pywikibot/site.py index 5ba136e..bee4bce 100644 --- a/pywikibot/site.py +++ b/pywikibot/site.py @@ -5821,16 +5821,19 @@ return data['flow']['view-topiclist']['result']['topiclist']
@need_extension('Flow') - def load_topic(self, page): + def load_topic(self, page, format): """Retrieve the data for a Flow topic.
@param page: A Flow topic @type page: Topic + @param format: The content format to request the data in. + @type format: str (either 'wikitext', 'html', or 'fixed-html') @return: A dict representing the topic's data. @rtype: dict """ req = self._simple_request(action='flow', page=page, - submodule='view-topic') + submodule='view-topic', + vtformat=format) data = req.submit() return data['flow']['view-topic']['result']['topic']
@@ -5853,6 +5856,30 @@ data = req.submit() return data['flow']['view-post']['result']['topic']
+ @must_be('user') + @need_extension('Flow') + def reply_to_post(self, page, reply_to_uuid, content, format): + """Reply to a post on a Flow topic. + + @param page: A Flow topic + @type page: Topic + @param reply_to_uuid: The UUID of the Post to create a reply to + @type reply_to_uuid: unicode + @param content: The content of the reply + @type content: unicode + @param format: The content format used for the supplied content + @type format: unicode (either 'wikitext' or 'html') + @return: Metadata returned by the API + @rtype: dict + """ + token = self.tokens['csrf'] + params = {'action': 'flow', 'page': page, 'token': token, + 'submodule': 'reply', 'repreplyTo': reply_to_uuid, + 'repcontent': content, 'repformat': format} + req = self._request(parameters=params, use_get=False) + data = req.submit() + return data['flow']['reply']['committed']['topic'] + def watched_pages(self, sysop=False, force=False, step=None, total=None): """ Return watchlist. diff --git a/tests/flow_edit_tests.py b/tests/flow_edit_tests.py new file mode 100644 index 0000000..863fb71 --- /dev/null +++ b/tests/flow_edit_tests.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- +"""Edit tests for the flow module.""" +# +# (C) Pywikibot team, 2015 +# +# Distributed under the terms of the MIT license. +# +from __future__ import unicode_literals + +__version__ = '$Id$' + +from pywikibot.flow import Topic, Post +from pywikibot.tools import PY2 + +from tests.aspects import TestCase + +if not PY2: + unicode = str + + +class TestFlowReply(TestCase): + + """Test replying to existing posts.""" + + family = 'test' + code = 'test' + + user = True + write = True + + def test_reply_to_topic(self): + """Test replying to "topic" (really the topic's root post).""" + # Setup + content = 'I am a reply to the topic. Replying works!' + topic = Topic(self.site, 'Topic:Sl4ssgh123c3e1bh') + old_replies = topic.replies(force=True)[:] + # Reply + reply_post = topic.reply(content, 'wikitext') + # Test content + wikitext = reply_post.get(format='wikitext') + self.assertIn('wikitext', reply_post._content) + self.assertNotIn('html', reply_post._content) + self.assertIsInstance(wikitext, unicode) + self.assertEqual(wikitext, content) + # Test reply list in topic + new_replies = topic.replies(force=True) + self.assertEqual(len(new_replies), len(old_replies) + 1) + + def test_reply_to_topic_root(self): + """Test replying to the topic's root post directly.""" + # Setup + content = "I am a reply to the topic's root post. Replying still works!" + topic = Topic(self.site, 'Topic:Sl4ssgh123c3e1bh') + topic_root = topic.root + old_replies = topic_root.replies(force=True)[:] + # Reply + reply_post = topic_root.reply(content, 'wikitext') + # Test content + wikitext = reply_post.get(format='wikitext') + self.assertIn('wikitext', reply_post._content) + self.assertNotIn('html', reply_post._content) + self.assertIsInstance(wikitext, unicode) + self.assertEqual(wikitext, content) + # Test reply list in topic + new_replies = topic_root.replies(force=True) + self.assertEqual(len(new_replies), len(old_replies) + 1) + + def test_reply_to_post(self): + """Test replying to an ordinary post.""" + # Setup + content = 'I am a nested reply to a regular post. Still going strong!' + topic = Topic(self.site, 'Topic:Sl4ssgh123c3e1bh') + root_post = Post(topic, 'smjnql768bl0h0kt') + old_replies = root_post.replies(force=True)[:] + # Reply + reply_post = root_post.reply(content, 'wikitext') + # Test content + wikitext = reply_post.get(format='wikitext') + self.assertIn('wikitext', reply_post._content) + self.assertNotIn('html', reply_post._content) + self.assertIsInstance(wikitext, unicode) + self.assertEqual(wikitext, content) + # Test reply list in topic + new_replies = root_post.replies(force=True) + self.assertEqual(len(new_replies), len(old_replies) + 1) + + def test_nested_reply(self): + """Test replying to a previous reply to a topic.""" + # Setup + first_content = 'I am a reply to the topic with my own replies. Great!' + second_content = 'I am a nested reply. This conversation is getting pretty good!' + topic = Topic(self.site, 'Topic:Sl4ssgh123c3e1bh') + topic_root = topic.root + # First reply + old_root_replies = topic_root.replies(force=True)[:] + first_reply_post = topic_root.reply(first_content, 'wikitext') + # Test first reply's content + first_wikitext = first_reply_post.get(format='wikitext') + self.assertIn('wikitext', first_reply_post._content) + self.assertNotIn('html', first_reply_post._content) + self.assertIsInstance(first_wikitext, unicode) + self.assertEqual(first_wikitext, first_content) + # Test reply list in topic + new_root_replies = topic_root.replies(force=True) + self.assertEqual(len(new_root_replies), len(old_root_replies) + 1) + + # Nested reply + old_nested_replies = first_reply_post.replies(force=True)[:] + self.assertListEqual(old_nested_replies, []) + second_reply_post = first_reply_post.reply(second_content, + 'wikitext') + # Test nested reply's content + second_wikitext = second_reply_post.get(format='wikitext') + self.assertIn('wikitext', second_reply_post._content) + self.assertNotIn('html', second_reply_post._content) + self.assertIsInstance(second_wikitext, unicode) + self.assertEqual(second_wikitext, second_content) + + # Test reply list in first reply + # Broken due to current Flow reply structure (T105438) + # new_nested_replies = first_reply_post.replies(force=True) + # self.assertEqual(len(new_nested_replies), len(old_nested_replies) + 1) + + # Current test for nested reply list + self.assertListEqual(old_nested_replies, []) + more_root_replies = topic_root.replies(force=True) + self.assertEqual(len(more_root_replies), len(new_root_replies) + 1)