Skip to content

Commit

Permalink
Merge efed44b into 54f8b3f
Browse files Browse the repository at this point in the history
  • Loading branch information
fwump38 committed Dec 1, 2019
2 parents 54f8b3f + efed44b commit 0900f81
Show file tree
Hide file tree
Showing 28 changed files with 4,352 additions and 1 deletion.
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,5 @@ Source Contributors
- vaclav-2012 `@vaclav-2012 <https://github.com/vaclav-2012>`_
- kungming2 `@kungming2 <https://github.com/kungming2>`_
- Jack Steel `@jackodsteel <https://github.com/jackodsteel>`_
- David Mirch `@fwump38 <https://github.com/fwump38>`_
- Add "Name <email (optional)> and github profile link" above this line.
8 changes: 8 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ Unreleased
* :meth:`.Reddit.redditor` supports ``fullname`` param to fetch a Redditor
by the fullname instead of name.
:class:`.Redditor` constructor now also has ``fullname`` param.
* Add :class:`.Reason` and :class:`.SubredditReasons` to work with removal
reasons
* Attribute ``reasons`` to :class:`.Subreddit` to interact with new removal
reason classes
* Add :meth:`.ThingModerationMixin.add_removal_reason` to add a removal
reason to a removed submission/comment
* Parameters ``reason_id`` and ``mod_note`` to
:meth:`.ThingModerationMixin.remove` to optionally apply a removal reason

6.4.0 (2019/09/21)
------------------
Expand Down
2 changes: 2 additions & 0 deletions docs/code_overview/other.rst
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,14 @@ instances of them bound to an attribute of one of the PRAW models.
other/modmail
other/modmailmessage
other/preferences
other/reason
other/redditbase
other/redditorlist
other/sublisting
other/submenu
other/subredditemoji
other/subredditmessage
other/subredditreasons
other/redditorstream
other/trophy
other/util
5 changes: 5 additions & 0 deletions docs/code_overview/other/reason.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Reason
======

.. autoclass:: praw.models.reddit.reasons.Reason
:inherited-members:
5 changes: 5 additions & 0 deletions docs/code_overview/other/subredditreasons.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
SubredditReasons
================

.. autoclass:: praw.models.reddit.reasons.SubredditReasons
:inherited-members:
3 changes: 3 additions & 0 deletions praw/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@
"read_message": "api/read_message/",
"removal_comment_message": "api/v1/modactions/removal_comment_message",
"removal_link_message": "api/v1/modactions/removal_link_message",
"removal_reasons": "api/v1/modactions/removal_reasons",
"removal_reason": "api/v1/{subreddit}/removal_reasons/{id}",
"removal_reasons_list": "api/v1/{subreddit}/removal_reasons",
"remove": "api/remove/",
"report": "api/report/",
"rules": "r/{subreddit}/about/rules",
Expand Down
1 change: 1 addition & 0 deletions praw/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from .reddit.modmail import ModmailAction, ModmailConversation, ModmailMessage
from .reddit.more import MoreComments
from .reddit.multi import Multireddit
from .reddit.reasons import Reason
from .reddit.redditor import Redditor
from .reddit.submission import Submission
from .reddit.subreddit import Subreddit
Expand Down
49 changes: 48 additions & 1 deletion praw/models/reddit/mixins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,16 @@ def lock(self):
API_PATH["lock"], data={"id": self.thing.fullname}
)

def remove(self, spam=False):
def remove(self, spam=False, reason_id=None, mod_note=""):
"""Remove a :class:`~.Comment` or :class:`~.Submission`.
:param spam: When True, use the removal to help train the Subreddit's
spam filter (default: False).
:param reason_id: The removal reason ID.
:param mod_note: A message for the other moderators.
If either reason_id or mod_note are provided, a second API call is made
using :meth:`~.add_removal_reason`
Example usage:
Expand All @@ -134,10 +139,52 @@ def remove(self, spam=False):
# remove a submission
submission = reddit.submission(id='5or86n')
submission.mod.remove()
# remove a submission with a removal reason
reason = reddit.subreddit.reasons["110ni21zo23ql"]
submission = reddit.submission(id="5or86n")
submission.mod.remove(reason_id=reason.id)
"""
data = {"id": self.thing.fullname, "spam": bool(spam)}
self.thing._reddit.post(API_PATH["remove"], data=data)
if any([reason_id, mod_note]):
self.add_removal_reason(reason_id, mod_note)

def add_removal_reason(self, reason_id=None, mod_note=""):
"""Add a removal reason for a Comment or Submission.
:param reason_id: The removal reason ID.
:param mod_note: A message for the other moderators.
It is necessary to first call :meth:`~.remove` on the
:class:`~.Comment` or :class:`~.Submission`.
If reason_id is not specified, mod_note cannot be blank.
Example usage:
.. code:: python
comment = reddit.comment("dkk4qjd")
comment.mod.add_removal_reason(reason_id="110ni21zo23ql")
# remove a submission and add a mod note
submission = reddit.submission(id="5or86n")
submission.mod.remove(reason_id="110ni21zo23ql", mod_note="foo")
"""
if not reason_id and not mod_note:
raise ValueError(
"mod_note cannot be blank if reason_id is not specified"
)
# Only the first element of the item_id list is used.
data = {
"item_ids": [self.thing.fullname],
"mod_note": mod_note,
"reason_id": reason_id,
}
self.thing._reddit.post(
API_PATH["removal_reasons"], data={"json": dumps(data)}
)

def send_removal_message(
self,
Expand Down
170 changes: 170 additions & 0 deletions praw/models/reddit/reasons.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
"""Provide the Reason class."""
from ...const import API_PATH
from ...exceptions import ClientException
from .base import RedditBase


class Reason(RedditBase):
"""An individual Reason object.
**Typical Attributes**
This table describes attributes that typically belong to objects of this
class. Since attributes are dynamically provided (see
:ref:`determine-available-attributes-of-an-object`), there is not a
guarantee that these attributes will always be present, nor is this list
necessarily comprehensive.
======================= ===================================================
Attribute Description
======================= ===================================================
``id`` The id of the removal reason.
``title`` The title of the removal reason.
``message`` The message of the removal reason.
======================= ===================================================
"""

STR_FIELD = "id"

def __eq__(self, other):
"""Return whether the other instance equals the current."""
if isinstance(other, str):
return other == str(self)
return (
isinstance(other, self.__class__)
and str(self) == str(other)
and other.subreddit == self.subreddit
)

def __hash__(self):
"""Return the hash of the current instance."""
return (
hash(self.__class__.__name__)
^ hash(str(self))
^ hash(self.subreddit)
)

def __init__(self, reddit, subreddit, reason_id, _data=None):
"""Construct an instance of the Reason object."""
self.id = reason_id
self.subreddit = subreddit
super(Reason, self).__init__(reddit, _data=_data)

def _fetch(self):
for reason in self.subreddit.reasons:
if reason.id == self.id:
self.__dict__.update(reason.__dict__)
self._fetched = True
return
raise ClientException(
"/r/{} does not have the reason {}".format(self.subreddit, self.id)
)


class SubredditReasons:
"""Provide a set of functions to a Subreddit's removal reasons."""

def __getitem__(self, reason_id):
"""Lazily return the Reason for the subreddit with id ``reason_id``.
:param reason_id: The id of the removal reason
This method is to be used to fetch a specific removal reason, like so:
.. code:: python
reason = reddit.subreddit('praw_test').reasons['141vv5c16py7d']
print(reason)
"""
return Reason(self._reddit, self.subreddit, reason_id)

def __init__(self, subreddit):
"""Create a SubredditReasons instance.
:param subreddit: The subreddit whose removal reasons to work with.
"""
self.subreddit = subreddit
self._reddit = subreddit._reddit

def __iter__(self):
"""Return a list of Removal Reasons for the subreddit.
This method is used to discover all removal reasons for a
subreddit:
.. code-block:: python
for reason in reddit.subreddit('NAME').reasons:
print(reason)
"""
response = self.subreddit._reddit.get(
API_PATH["removal_reasons_list"].format(subreddit=self.subreddit)
)
for reason_id, reason_data in response["data"].items():
yield Reason(
self._reddit, self.subreddit, reason_id, _data=reason_data
)

def add(self, title, message):
"""Add a removal reason to this subreddit.
:param title: The title of the removal reason
:param message: The message to send the user.
:returns: The Reason added.
The message will be prepended with `Hi u/username,` automatically.
To add ``'test'`` to the subreddit ``'praw_test'`` try:
.. code:: python
reddit.subreddit('praw_test').reason.add('test','This is a test')
"""
data = {"title": title, "message": message}
url = API_PATH["removal_reasons_list"].format(subreddit=self.subreddit)
reason_id = self.subreddit._reddit.post(url, data=data)
return Reason(self._reddit, self.subreddit, reason_id)

def update(self, reason_id, title, message):
"""Update the removal reason from this subreddit by ``reason_id``.
:param reason_id: The id of the removal reason
:param title: The removal reason's new title (required).
:param message: The removal reason's new message (required).
To update ``'141vv5c16py7d'`` from the subreddit ``'praw_test'`` try:
.. code:: python
reddit.subreddit('praw_test').reason.update(
'141vv5c16py7d',
'New Title',
'New message')
"""
url = API_PATH["removal_reason"].format(
subreddit=self.subreddit, id=reason_id
)
data = {"title": title, "message": message}
self.subreddit._reddit.put(url, data=data)

def delete(self, reason_id):
"""Delete a removal reason from this subreddit.
:param reason_id: The id of the removal reason to remove
To delete ``'141vv5c16py7d'`` from the subreddit ``'praw_test'`` try:
.. code:: python
reddit.subreddit('praw_test').reason.delete('141vv5c16py7d')
"""
url = API_PATH["removal_reason"].format(
subreddit=self.subreddit, id=reason_id
)
self.subreddit._reddit.request("DELETE", url)
26 changes: 26 additions & 0 deletions praw/models/reddit/subreddit.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from .emoji import SubredditEmoji
from .mixins import FullnameMixin, MessageableMixin
from .modmail import ModmailConversation
from .reasons import SubredditReasons
from .widgets import SubredditWidgets, WidgetEncoder
from .wikipage import WikiPage

Expand Down Expand Up @@ -349,6 +350,31 @@ def quaran(self):
"""
return SubredditQuarantine(self)

@cachedproperty
def reasons(self):
"""Provide an instance of :class:`.SubredditReasons`.
Use this attribute for interacting with a subreddit's removal reasons.
For example to list all the removal reaons for a subreddit which you
have the ``posts`` moderator permission on try:
.. code-block:: python
for reason in reddit.subreddit('NAME').reasons:
print(reason)
A single removal reason can be lazily retrieved via:
.. code:: python
reddit.subreddit('NAME').reasons['title']
.. note:: Attempting to access attributes of an nonexistent removal
reason will result in a :class:`.ClientException`.
"""
return SubredditReasons(self)

@cachedproperty
def stream(self):
"""Provide an instance of :class:`.SubredditStream`.
Expand Down
Loading

0 comments on commit 0900f81

Please sign in to comment.