Skip to content

Commit 884285e

Browse files
committed
news CUD operations support
1 parent 1c10bbb commit 884285e

7 files changed

Lines changed: 196 additions & 14 deletions

File tree

CHANGELOG.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
Changelog
22
---------
33

4-
2.2.2 (2019-08-XX)
4+
2.3.0 (2020-06-XX)
55
++++++++++++++++++
66

77
**Improvements**:
88

9+
- News ``create()``, ``update()``, ``delete()`` operations support (requires Redmine >= 4.1.0)
910
- ResourceSet's ``export()`` method now supports ``columns`` keyword argument which can be either an iterable
1011
of column names, an "all" string which tells Python-Redmine to export all available columns, "all_gui" string
1112
for GUI like behaviour or iterable of elements with "all_gui" string and additional columns to export

docs/resources/news.rst

Lines changed: 140 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,60 @@ See the :doc:`../configuration` about how to configure redmine object.
1313
Create methods
1414
--------------
1515

16-
Not supported by Redmine
16+
.. versionadded:: 2.3.0
17+
18+
create
19+
++++++
20+
21+
.. py:method:: create(**fields)
22+
:module: redminelib.managers.ResourceManager
23+
:noindex:
24+
25+
Creates new News resource with given fields and saves it to the Redmine.
26+
27+
:param project_id: (required). Id or identifier of News's project.
28+
:type project_id: int or string
29+
:param string title: (required). News title.
30+
:param string description: (required). News description.
31+
:param string summary: (optional). News summary.
32+
:param list uploads:
33+
.. raw:: html
34+
35+
(optional). Uploads as [{'': ''}, ...], accepted keys are:
36+
37+
- path (required). Absolute file path or file-like object that should be uploaded.
38+
- filename (optional). Name of the file after upload.
39+
- description (optional). Description of the file.
40+
- content_type (optional). Content type of the file.
41+
42+
:return: :ref:`Resource` object
43+
44+
.. code-block:: python
45+
46+
>>> news = redmine.news.create(title='Foo', description='foobar')
47+
>>> news
48+
<redminelib.resources.News #8 "Foo">
49+
50+
new
51+
+++
52+
53+
.. py:method:: new()
54+
:module: redminelib.managers.ResourceManager
55+
:noindex:
56+
57+
Creates new empty News resource but saves it to the Redmine only when ``save()`` is called, also
58+
calls ``pre_create()`` and ``post_create()`` methods of the :ref:`Resource` object. Valid attributes
59+
are the same as for ``create()`` method above.
60+
61+
:return: :ref:`Resource` object
62+
63+
.. code-block:: python
64+
65+
>>> news = redmine.news.new()
66+
>>> news.title = 'Foo'
67+
>>> news.description = 'foobar'
68+
>>> news.save()
69+
<redminelib.resources.News #8 "Foo">
1770
1871
Read methods
1972
------------
@@ -91,12 +144,96 @@ filter
91144
Update methods
92145
--------------
93146

94-
Not supported by Redmine
147+
.. versionadded:: 2.3.0
148+
149+
update
150+
++++++
151+
152+
.. py:method:: update(resource_id, **fields)
153+
:module: redminelib.managers.ResourceManager
154+
:noindex:
155+
156+
Updates values of given fields of a News resource and saves them to the Redmine.
157+
158+
:param int resource_id: (required). News id.
159+
:param string title: (optional). News title.
160+
:param string description: (optional). News description.
161+
:param string summary: (optional). News summary.
162+
:return: True
163+
164+
.. code-block:: python
165+
166+
>>> redmine.news.update(1, title='Bar', description='barfoo', summary='bar')
167+
True
168+
169+
save
170+
++++
171+
172+
.. py:method:: save(**attrs)
173+
:module: redminelib.resources.News
174+
:noindex:
175+
176+
Saves current state of a News resource to the Redmine. Attrs that can be
177+
changed are the same as for ``update()`` method above.
178+
179+
:return: :ref:`Resource` object
180+
181+
.. code-block:: python
182+
183+
>>> news = redmine.news.get(1)
184+
>>> news.title = 'Bar'
185+
>>> news.description = 'barfoo'
186+
>>> news.summary = 'bar'
187+
>>> news.save()
188+
<redminelib.resources.News #1 "Bar">
189+
190+
.. versionadded:: 2.1.0 Alternative syntax was introduced.
191+
192+
.. code-block:: python
193+
194+
>>> news = redmine.news.get(1).save(
195+
... title='Bar',
196+
... description='barfoo',
197+
... summary='bar'
198+
... )
199+
>>> news
200+
<redminelib.resources.News #1 "Bar">
95201
96202
Delete methods
97203
--------------
98204

99-
Not supported by Redmine
205+
.. versionadded:: 2.3.0
206+
207+
delete
208+
++++++
209+
210+
.. py:method:: delete(resource_id)
211+
:module: redminelib.managers.ResourceManager
212+
:noindex:
213+
214+
Deletes single News resource from Redmine by it's id.
215+
216+
:param int resource_id: (required). News id.
217+
:return: True
218+
219+
.. code-block:: python
220+
221+
>>> redmine.news.delete(1)
222+
True
223+
224+
.. py:method:: delete()
225+
:module: redminelib.resources.News
226+
:noindex:
227+
228+
Deletes current News resource object from Redmine.
229+
230+
:return: True
231+
232+
.. code-block:: python
233+
234+
>>> news = redmine.news.get(1)
235+
>>> news.delete()
236+
True
100237
101238
Export
102239
------

redminelib/managers/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
"""
44

55
from .base import ResourceManager
6-
from .standard import WikiPageManager, FileManager
6+
from .standard import WikiPageManager, FileManager, NewsManager

redminelib/managers/standard.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,11 @@ def _process_create_response(self, request, response):
2020
response = {self.container: {'id': int(request[self.container]['token'].split('.')[0])}}
2121

2222
return super(FileManager, self)._process_create_response(request, response)
23+
24+
25+
class NewsManager(ResourceManager):
26+
def _process_create_response(self, request, response):
27+
if response is True:
28+
response = {self.container: self.redmine.news.filter(**self.params)[0].raw()}
29+
30+
return super(NewsManager, self)._process_create_response(request, response)

redminelib/resources/standard.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,12 +489,20 @@ class Role(BaseResource):
489489
class News(BaseResource):
490490
redmine_version = '1.1'
491491
container_all = 'news'
492+
container_one = 'news'
492493
container_filter = 'news'
494+
container_create = 'news'
495+
container_update = 'news'
493496
query_all_export = '/news.{format}'
494497
query_all = '/news.json'
498+
query_one = '/news/{0}.json'
495499
query_filter = '/news.json'
500+
query_create = '/projects/{project_id}/news.json'
501+
query_update = '/news/{0}.json'
502+
query_delete = '/news/{0}.json'
496503
query_url = '/news/{0}'
497504
search_hints = ['news']
505+
manager_class = managers.NewsManager
498506

499507
_repr = [['id', 'title']]
500508
_resource_map = {'project': 'Project', 'author': 'User'}

tests/responses/standard.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,9 @@
5757
'all': {'roles': [{'name': 'Foo', 'id': 1}, {'name': 'Bar', 'id': 2}]},
5858
},
5959
'news': {
60-
'all': {'news': [{'title': 'Foo', 'id': 1}, {'title': 'Bar', 'id': 2}]},
61-
'filter': {'news': [{'title': 'Foo', 'id': 1}, {'title': 'Bar', 'id': 2}]},
60+
'get': {'news': {'title': 'Foo', 'id': 1}},
61+
'all': {'news': [{'title': 'Foo', 'id': 2}, {'title': 'Bar', 'id': 1}]},
62+
'filter': {'news': [{'title': 'Foo', 'id': 2}, {'title': 'Bar', 'id': 1}]},
6263
},
6364
'issue_status': {
6465
'all': {'issue_statuses': [{'name': 'Foo', 'id': 1}, {'name': 'Bar', 'id': 2}]},

tests/test_resources_standard.py

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1189,30 +1189,57 @@ def test_news_version(self):
11891189
self.assertEqual(self.redmine.news.resource_class.redmine_version, '1.1')
11901190

11911191
def test_news_get(self):
1192-
self.response.json.return_value = responses['news']['all']
1192+
self.response.json.return_value = responses['news']['get']
11931193
news = self.redmine.news.get(1)
11941194
self.assertEqual(news.id, 1)
11951195
self.assertEqual(news.title, 'Foo')
11961196

11971197
def test_news_all(self):
11981198
self.response.json.return_value = responses['news']['all']
11991199
news = self.redmine.news.all()
1200-
self.assertEqual(news[0].id, 1)
1200+
self.assertEqual(news[0].id, 2)
12011201
self.assertEqual(news[0].title, 'Foo')
1202-
self.assertEqual(news[1].id, 2)
1202+
self.assertEqual(news[1].id, 1)
12031203
self.assertEqual(news[1].title, 'Bar')
12041204

12051205
def test_news_filter(self):
12061206
self.response.json.return_value = responses['news']['filter']
12071207
news = self.redmine.news.filter(project_id=1)
1208-
self.assertEqual(news[0].id, 1)
1208+
self.assertEqual(news[0].id, 2)
12091209
self.assertEqual(news[0].title, 'Foo')
1210-
self.assertEqual(news[1].id, 2)
1210+
self.assertEqual(news[1].id, 1)
12111211
self.assertEqual(news[1].title, 'Bar')
12121212

1213+
def test_news_create(self):
1214+
self.response.status_code = 201
1215+
self.response.json.return_value = responses['news']['get']
1216+
news = self.redmine.news.create(project_id=1, title='Foo')
1217+
self.assertEqual(news.title, 'Foo')
1218+
1219+
def test_news_create_empty_response(self):
1220+
self.set_patch_side_effect([
1221+
mock.Mock(status_code=204, history=[], content=''),
1222+
mock.Mock(status_code=201, history=[], **{'json.return_value': responses['news']['filter']})
1223+
])
1224+
news = self.redmine.news.create(project_id=1, title='Foo')
1225+
self.assertEqual(news.title, 'Foo')
1226+
1227+
def test_news_delete(self):
1228+
self.response.json.return_value = responses['news']['get']
1229+
news = self.redmine.news.get(1)
1230+
self.response.content = ''
1231+
self.assertEqual(news.delete(), True)
1232+
self.assertEqual(self.redmine.news.delete(1), True)
1233+
1234+
def test_news_update(self):
1235+
self.response.json.return_value = responses['news']['get']
1236+
news = self.redmine.news.get(1)
1237+
news.title = 'Bar'
1238+
self.assertIsInstance(news.save(), resources.News)
1239+
12131240
def test_news_url(self):
12141241
self.response.json.return_value = responses['news']['filter']
1215-
self.assertEqual(self.redmine.news.filter(project_id=1)[0].url, '{0}/news/1'.format(self.url))
1242+
self.assertEqual(self.redmine.news.filter(project_id=1)[0].url, '{0}/news/2'.format(self.url))
12161243

12171244
@mock.patch('redminelib.open', mock.mock_open(), create=True)
12181245
def test_news_export(self):
@@ -1226,7 +1253,7 @@ def test_news_str(self):
12261253

12271254
def test_news_repr(self):
12281255
self.response.json.return_value = responses['news']['filter']
1229-
self.assertEqual(repr(self.redmine.news.filter(project_id=1)[0]), '<redminelib.resources.News #1 "Foo">')
1256+
self.assertEqual(repr(self.redmine.news.filter(project_id=1)[0]), '<redminelib.resources.News #2 "Foo">')
12301257

12311258
def test_issue_status_version(self):
12321259
self.assertEqual(self.redmine.issue_status.resource_class.redmine_version, '1.3')

0 commit comments

Comments
 (0)