jenkins-bot has submitted this change and it was merged.
Change subject: Moderate Flow topics and posts ......................................................................
Moderate Flow topics and posts
This change allows Pywikibot bots to moderate topics and posts using Flow's moderation system.
Bug: T109308 Change-Id: I92f7c664d13f8397219a4045d782f4b1123610f5 --- M pywikibot/flow.py M pywikibot/site.py M tests/flow_edit_tests.py 3 files changed, 375 insertions(+), 5 deletions(-)
Approvals: John Vandenberg: Looks good to me, approved jenkins-bot: Verified
diff --git a/pywikibot/flow.py b/pywikibot/flow.py index accc5d4..9ccb5f5 100644 --- a/pywikibot/flow.py +++ b/pywikibot/flow.py @@ -164,6 +164,10 @@ self._data = self.site.load_topic(self, format) return self._data
+ def _reload(self): + """Forcibly reload the topic's root post.""" + self.root._load(load_from_topic=True) + @classmethod def create_topic(cls, board, title, content, format='wikitext'): """Create and return a Topic object for a new topic on a Board. @@ -219,6 +223,11 @@ """Whether this topic is locked.""" return self.root._current_revision['isLocked']
+ @property + def is_moderated(self): + """Whether this topic is moderated.""" + return self.root._current_revision['isModerated'] + def replies(self, format='wikitext', force=False): """A list of replies to this topic's root post.
@@ -243,23 +252,60 @@ """ return self.root.reply(content, format)
- def lock(self, reason='Closed'): + # Moderation + def lock(self, reason): """Lock this topic.
@param reason: The reason for locking this topic @type reason: unicode """ self.site.lock_topic(self, True, reason) - self.root._load(load_from_topic=True) + self._reload()
- def unlock(self, reason='Reopened'): + def unlock(self, reason): """Unlock this topic.
@param reason: The reason for unlocking this topic @type reason: unicode """ self.site.lock_topic(self, False, reason) - self.root._load(load_from_topic=True) + self._reload() + + def delete_mod(self, reason): + """Delete this topic through the Flow moderation system. + + @param reason: The reason for deleting this topic. + @type reason: unicode + """ + self.site.delete_topic(self, reason) + self._reload() + + def hide(self, reason): + """Hide this topic. + + @param reason: The reason for hiding this topic. + @type reason: unicode + """ + self.site.hide_topic(self, reason) + self._reload() + + def suppress(self, reason): + """Suppress this topic. + + @param reason: The reason for suppressing this topic. + @type reason: unicode + """ + self.site.suppress_topic(self, reason) + self._reload() + + def restore(self, reason): + """Restore this topic. + + @param reason: The reason for restoring this topic. + @type reason: unicode + """ + self.site.restore_topic(self, reason) + self._reload()
# Flow non-page-like objects @@ -375,6 +421,13 @@ """ return self._page
+ @property + def is_moderated(self): + """Whether this post is moderated.""" + if not hasattr(self, '_current_revision'): + self._load() + return self._current_revision['isModerated'] + def get(self, format='wikitext', force=False, sysop=False): """Return the contents of the post in the given format.
@@ -445,3 +498,40 @@ data = self.site.reply_to_post(self.page, reply_to, content, format) post = Post(self.page, data['post-id']) return post + + # Moderation + def delete(self, reason): + """Delete this post through the Flow moderation system. + + @param reason: The reason for deleting this post. + @type reason: unicode + """ + self.site.delete_post(self, reason) + self._load() + + def hide(self, reason): + """Hide this post. + + @param reason: The reason for hiding this post. + @type reason: unicode + """ + self.site.hide_post(self, reason) + self._load() + + def suppress(self, reason): + """Suppress this post. + + @param reason: The reason for suppressing this post. + @type reason: unicode + """ + self.site.suppress_post(self, reason) + self._load() + + def restore(self, reason): + """Restore this post. + + @param reason: The reason for restoring this post. + @type reason: unicode + """ + self.site.restore_post(self, reason) + self._load() diff --git a/pywikibot/site.py b/pywikibot/site.py index 25dedf7..bd319d5 100644 --- a/pywikibot/site.py +++ b/pywikibot/site.py @@ -6325,7 +6325,7 @@ data = req.submit() return data['flow']['reply']['committed']['topic']
- @must_be('user') + @must_be('user', 'flow-lock') @need_extension('Flow') def lock_topic(self, page, lock, reason): """Lock or unlock a Flow topic. @@ -6348,6 +6348,164 @@ data = req.submit() return data['flow']['lock-topic']['committed']['topic']
+ @must_be('user') + @need_extension('Flow') + def moderate_topic(self, page, state, reason): + """Moderate a Flow topic. + + @param page: A Flow topic + @type page: Topic + @param state: The new moderation state + @type state: str + @param reason: The reason to moderate the topic + @type reason: unicode + @return: Metadata returned by the API + @rtype: dict + """ + token = self.tokens['csrf'] + params = {'action': 'flow', 'page': page, 'token': token, + 'submodule': 'moderate-topic', 'mtreason': reason, + 'mtmoderationState': state} + req = self._request(parameters=params, use_get=False) + data = req.submit() + return data['flow']['moderate-topic']['committed']['topic'] + + @must_be('user', 'flow-delete') + @need_extension('Flow') + def delete_topic(self, page, reason): + """Delete a Flow topic. + + @param page: A Flow topic + @type page: Topic + @param reason: The reason to delete the topic + @type reason: unicode + @return: Metadata returned by the API + @rtype: dict + """ + return self.moderate_topic(page, 'delete', reason) + + @must_be('user', 'flow-hide') + @need_extension('Flow') + def hide_topic(self, page, reason): + """Hide a Flow topic. + + @param page: A Flow topic + @type page: Topic + @param reason: The reason to hide the topic + @type reason: unicode + @return: Metadata returned by the API + @rtype: dict + """ + return self.moderate_topic(page, 'hide', reason) + + @must_be('user', 'flow-suppress') + @need_extension('Flow') + def suppress_topic(self, page, reason): + """Suppress a Flow topic. + + @param page: A Flow topic + @type page: Topic + @param reason: The reason to suppress the topic + @type reason: unicode + @return: Metadata returned by the API + @rtype: dict + """ + return self.moderate_topic(page, 'suppress', reason) + + @must_be('user') + @need_extension('Flow') + def restore_topic(self, page, reason): + """Restore a Flow topic. + + @param page: A Flow topic + @type page: Topic + @param reason: The reason to restore the topic + @type reason: unicode + @return: Metadata returned by the API + @rtype: dict + """ + return self.moderate_topic(page, 'restore', reason) + + @must_be('user') + @need_extension('Flow') + def moderate_post(self, post, state, reason): + """Moderate a Flow post. + + @param post: A Flow post + @type post: Post + @param state: The new moderation state + @type state: str + @param reason: The reason to moderate the topic + @type reason: unicode + @return: Metadata returned by the API + @rtype: dict + """ + page = post.page + uuid = post.uuid + token = self.tokens['csrf'] + params = {'action': 'flow', 'page': page, 'token': token, + 'submodule': 'moderate-post', 'mpreason': reason, + 'mpmoderationState': state, 'mppostId': uuid} + req = self._request(parameters=params, use_get=False) + data = req.submit() + return data['flow']['moderate-post']['committed']['topic'] + + @must_be('user', 'flow-delete') + @need_extension('Flow') + def delete_post(self, post, reason): + """Delete a Flow post. + + @param post: A Flow post + @type post: Post + @param reason: The reason to delete the post + @type reason: unicode + @return: Metadata returned by the API + @rtype: dict + """ + return self.moderate_post(post, 'delete', reason) + + @must_be('user', 'flow-hide') + @need_extension('Flow') + def hide_post(self, post, reason): + """Hide a Flow post. + + @param post: A Flow post + @type post: Post + @param reason: The reason to hide the post + @type reason: unicode + @return: Metadata returned by the API + @rtype: dict + """ + return self.moderate_post(post, 'hide', reason) + + @must_be('user', 'flow-suppress') + @need_extension('Flow') + def suppress_post(self, post, reason): + """Suppress a Flow post. + + @param post: A Flow post + @type post: Post + @param reason: The reason to suppress the post + @type reason: unicode + @return: Metadata returned by the API + @rtype: dict + """ + return self.moderate_post(post, 'suppress', reason) + + @must_be('user') + @need_extension('Flow') + def restore_post(self, post, reason): + """Restore a Flow post. + + @param post: A Flow post + @type post: Post + @param reason: The reason to restore the post + @type reason: unicode + @return: Metadata returned by the API + @rtype: dict + """ + return self.moderate_post(post, 'restore', reason) + 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 index 899274c..5ae7a54 100644 --- a/tests/flow_edit_tests.py +++ b/tests/flow_edit_tests.py @@ -173,6 +173,128 @@ self.assertFalse(topic.is_locked)
+class TestFlowHide(TestCase): + + """Hiding topics and posts.""" + + family = 'test' + code = 'test' + + user = True + write = True + + def test_hide_topic(self): + """Hide and restore a test topic.""" + # Setup + topic = Topic(self.site, 'Topic:Sl4svodmrhzmpjjh') + if topic.is_moderated: + topic.restore('Pywikibot test') + self.assertFalse(topic.is_moderated) + # Hide + topic.hide('Pywikibot test') + self.assertTrue(topic.is_moderated) + # Restore + topic.restore('Pywikibot test') + self.assertFalse(topic.is_moderated) + + def test_hide_post(self): + """Hide and restore a test post.""" + # Setup + topic = Topic(self.site, 'Topic:Sl4svodmrhzmpjjh') + post = Post(topic, 'sq1qvoig1az8w7cd') + if post.is_moderated: + post.restore('Pywikibot test') + self.assertFalse(post.is_moderated) + # Hide + post.hide('Pywikibot test') + self.assertTrue(post.is_moderated) + # Restore + post.restore('Pywikibot test') + self.assertFalse(post.is_moderated) + + +class TestFlowDelete(TestCase): + + """Deleting topics and posts.""" + + family = 'test' + code = 'test' + + user = True + write = True + sysop = True + + def test_delete_topic(self): + """Delete and restore a test topic.""" + # Setup + topic = Topic(self.site, 'Topic:Sl4svodmrhzmpjjh') + if topic.is_moderated: + topic.restore('Pywikibot test') + self.assertFalse(topic.is_moderated) + # Delete + topic.delete_mod('Pywikibot test') + self.assertTrue(topic.is_moderated) + # Restore + topic.restore('Pywikibot test') + self.assertFalse(topic.is_moderated) + + def test_delete_post(self): + """Delete and restore a test post.""" + # Setup + topic = Topic(self.site, 'Topic:Sl4svodmrhzmpjjh') + post = Post(topic, 'sq1qvoig1az8w7cd') + if post.is_moderated: + post.restore('Pywikibot test') + self.assertFalse(post.is_moderated) + # Delete + post.delete('Pywikibot test') + self.assertTrue(post.is_moderated) + # Restore + post.restore('Pywikibot test') + self.assertFalse(post.is_moderated) + + +class TestFlowSuppress(TestCase): + + """Suppressing topics and posts.""" + + family = 'test' + code = 'test' + + user = True + write = True + sysop = True + + def test_suppress_post(self): + """Suppress and restore a test post.""" + # Setup + topic = Topic(self.site, 'Topic:Sl4svodmrhzmpjjh') + post = Post(topic, 'sq1qvoig1az8w7cd') + if post.is_moderated: + post.restore('Pywikibot test') + self.assertFalse(post.is_moderated) + # Suppress + post.suppress('Pywikibot test') + self.assertTrue(post.is_moderated) + # Restore + post.restore('Pywikibot test') + self.assertFalse(post.is_moderated) + + def test_suppress_topic(self): + """Suppress and restore a test topic.""" + # Setup + topic = Topic(self.site, 'Topic:Sl4svodmrhzmpjjh') + if topic.is_moderated: + topic.restore('Pywikibot test') + self.assertFalse(topic.is_moderated) + # Suppress + topic.suppress('Pywikibot test') + self.assertTrue(topic.is_moderated) + # Restore + topic.restore('Pywikibot test') + self.assertFalse(topic.is_moderated) + + class TestFlowEditFailure(TestCase):
"""Flow-related edit failure tests."""
pywikibot-commits@lists.wikimedia.org