Skip to content

Commit d98864b

Browse files
committed
issue #117
1 parent baf910d commit d98864b

9 files changed

Lines changed: 379 additions & 5 deletions

File tree

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ Changelog
44
2.1.0 (2018-05-XX)
55
++++++++++++++++++
66

7+
**New Features**:
8+
9+
- Files API support (`Issue #117 <https://github.com/maxtepkeev/python-redmine/issues/117>`__)
10+
711
**Improvements**:
812

913
- ResourceSet's ``filter()`` method became more advanced. It is now possible to filter on all available

docs/resources/file.rst

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
File
2+
====
3+
4+
Supported by Redmine starting from version 3.4
5+
6+
Manager
7+
-------
8+
9+
All operations on the File resource are provided by it's manager. To get access to it
10+
you have to call ``redmine.file`` where ``redmine`` is a configured redmine object.
11+
See the :doc:`../configuration` about how to configure redmine object.
12+
13+
Create methods
14+
--------------
15+
16+
create
17+
++++++
18+
19+
.. py:method:: create(**fields)
20+
:module: redminelib.managers.FileManager
21+
:noindex:
22+
23+
Creates new File resource with given fields and saves it to the Redmine.
24+
25+
:param project_id: (required). Id or identifier of file's project.
26+
:type project_id: int or string
27+
:param string path: (required). Absolute file path or file-like object that should be uploaded.
28+
:param string filename: (optional). Name of the file after upload.
29+
:param string description: (optional). Description of the file.
30+
:param string content_type: (optional). Content type of the file.
31+
:param int version_id: (optional). File's version id.
32+
:return: :ref:`Resource` object
33+
34+
.. code-block:: python
35+
36+
>>> f = redmine.file.create(
37+
... project_id='vacation',
38+
... path='/absolute/path/to/file',
39+
... filename='foo.txt',
40+
... description='foobar',
41+
... content_type='text/plain',
42+
... version_id=1
43+
... )
44+
>>> f
45+
<redminelib.resources.File #1 "foo.txt">
46+
47+
new
48+
+++
49+
50+
.. py:method:: new()
51+
:module: redminelib.managers.FileManager
52+
:noindex:
53+
54+
Creates new empty File resource but saves it to the Redmine only when ``save()`` is called, also
55+
calls ``pre_create()`` and ``post_create()`` methods of the :ref:`Resource` object. Valid attributes
56+
are the same as for ``create()`` method above.
57+
58+
:return: :ref:`Resource` object
59+
60+
.. code-block:: python
61+
62+
>>> f = redmine.file.new()
63+
>>> f.project_id = 'vacation'
64+
>>> f.path = '/absolute/path/to/file'
65+
>>> f.filename = 'foo.txt'
66+
>>> f.description = 'foobar'
67+
>>> f.content_type = 'text/plain'
68+
>>> f.version_id = 1
69+
>>> f.save()
70+
<redminelib.resources.File #1 "foo.txt">
71+
72+
.. warning::
73+
74+
Redmine's File API doesn't return a file object after create operation. Due to the fact that it goes
75+
against the behaviour of all other API endpoints, Python-Redmine has to do some tricks under the hood
76+
to return a resource object with at least an ``id`` attribute. That doesn't involve any additional API
77+
requests. In most cases that should be enough, but if it's not and a complete resource object is needed,
78+
one has to use a ``refresh()`` method to make an additional query to the Redmine to retrieve a complete
79+
resource:
80+
81+
.. code-block:: python
82+
83+
>>> f = redmine.file.new()
84+
>>> f.project_id = 'vacation'
85+
>>> f.path = '/absolute/path/to/file'
86+
>>> f.filename = 'foo.txt'
87+
>>> f.description = 'foobar'
88+
>>> f.content_type = 'text/plain'
89+
>>> f.version_id = 1
90+
>>> f.save()
91+
<redminelib.resources.File #1>
92+
>>> f.refresh()
93+
>>> f
94+
<redminelib.resources.File #1 "foo.txt">
95+
96+
Read methods
97+
------------
98+
99+
get
100+
+++
101+
102+
.. py:method:: get(resource_id)
103+
:module: redminelib.managers.FileManager
104+
:noindex:
105+
106+
Returns single File resource from Redmine by it's id.
107+
108+
:param int resource_id: (required). Id of the file.
109+
:return: :ref:`Resource` object
110+
111+
.. code-block:: python
112+
113+
>>> f = redmine.file.get(12345)
114+
>>> f
115+
<redminelib.resources.File #12345 "foo.txt">
116+
117+
.. hint::
118+
119+
Files can be easily downloaded via the provided ``download()`` method which is a proxy
120+
to the ``redmine.download()`` method which provides several options to control the saving
121+
process (see `docs <https://python-redmine.com/advanced/working_with_files.html#
122+
download>`_ for details):
123+
124+
.. code-block:: python
125+
126+
>>> f = redmine.file.get(12345)
127+
>>> filepath = f.download(savepath='/usr/local/', filename='image.jpg')
128+
>>> filepath
129+
'/usr/local/image.jpg'
130+
131+
all
132+
+++
133+
134+
Not supported by Redmine
135+
136+
filter
137+
++++++
138+
139+
.. py:method:: filter(**filters)
140+
:module: redminelib.managers.FileManager
141+
:noindex:
142+
143+
Returns File resources that match the given lookup parameters.
144+
145+
:param project_id: (optional). Get files from the project with given id.
146+
:type project_id: int or string
147+
:return: ResourceSet object
148+
149+
.. code-block:: python
150+
151+
>>> files = redmine.file.filter(project_id='vacation')
152+
>>> files
153+
<redminelib.resultsets.ResourceSet object with File resources>
154+
155+
.. hint::
156+
157+
You can also get files from a Project resource object directly using ``files`` relation:
158+
159+
.. code-block:: python
160+
161+
>>> project = redmine.project.get('vacation')
162+
>>> project.files
163+
<redminelib.resultsets.ResourceSet object with File resources>
164+
165+
Update methods
166+
--------------
167+
168+
update
169+
++++++
170+
171+
.. py:method:: update(resource_id, **fields)
172+
:module: redminelib.managers.FileManager
173+
:noindex:
174+
175+
Updates values of given fields of a File resource and saves them to the Redmine.
176+
177+
:param int resource_id: (required). File id.
178+
:param string filename: (optional). File name.
179+
:param string description: (optional). File description.
180+
:param string content_type: (optional). File content-type.
181+
:return: True
182+
183+
.. code-block:: python
184+
185+
>>> redmine.file.update(
186+
... 1,
187+
... filename='foo.txt',
188+
... description='foobar',
189+
... content_type='text/plain'
190+
... )
191+
True
192+
193+
save
194+
++++
195+
196+
.. py:method:: save(**attrs)
197+
:module: redminelib.resources.File
198+
:noindex:
199+
200+
Saves the current state of a File resource to the Redmine. Attrs that can
201+
be changed are the same as for ``update()`` method above.
202+
203+
:return: :ref:`Resource` object
204+
205+
.. code-block:: python
206+
207+
>>> f = redmine.file.get(1)
208+
>>> f.filename = 'foo.txt'
209+
>>> f.description = 'foobar'
210+
>>> f.content_type = 'text/plain'
211+
>>> f.save()
212+
<redminelib.resources.File #1 "foo.txt">
213+
214+
.. versionadded:: 2.1.0 Alternative syntax was introduced.
215+
216+
.. code-block:: python
217+
218+
>>> f = redmine.file.get(1).save(
219+
... filename='foo.txt',
220+
... description='foobar',
221+
... content_type='text/plain'
222+
... )
223+
>>> f
224+
<redminelib.resources.File #1 "foo.txt">
225+
226+
Delete methods
227+
--------------
228+
229+
delete
230+
++++++
231+
232+
.. py:method:: delete(resource_id)
233+
:module: redminelib.managers.FileManager
234+
:noindex:
235+
236+
Deletes single File resource from Redmine by it's id.
237+
238+
:param int resource_id: (required). File id.
239+
:return: True
240+
241+
.. code-block:: python
242+
243+
>>> redmine.file.delete(12345)
244+
True
245+
246+
.. py:method:: delete()
247+
:module: redminelib.resources.File
248+
:noindex:
249+
250+
Deletes current File resource object from Redmine.
251+
252+
:return: True
253+
254+
.. code-block:: python
255+
256+
>>> f = redmine.file.get(12345)
257+
>>> f.delete()
258+
True
259+
260+
Export
261+
------
262+
263+
Export functionality doesn't make sense for files as they can be downloaded

docs/resources/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Redmine
1717
version
1818
wiki_page
1919
query
20+
file
2021
attachment
2122
issue_status
2223
tracker

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
6+
from .standard import WikiPageManager, FileManager

redminelib/managers/standard.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,11 @@ def _process_create_response(self, request, response):
1212
raise exceptions.ValidationError('Resource already exists') # issue #182
1313

1414
return super(WikiPageManager, self)._process_create_response(request, response)
15+
16+
17+
class FileManager(ResourceManager):
18+
def _process_create_response(self, request, response):
19+
if response is True:
20+
response = {self.container: {'id': int(request[self.container]['token'].split('.')[0])}}
21+
22+
return super(FileManager, self)._process_create_response(request, response)

redminelib/resources/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
"""
44

55
from .base import BaseResource, registry
6-
from .standard import (Project, Issue, TimeEntry, Enumeration, Attachment, IssueJournal, WikiPage, ProjectMembership,
7-
IssueCategory, IssueRelation, Version, User, Group, Role, News, IssueStatus, Tracker, Query,
8-
CustomField)
6+
from .standard import (Project, Issue, TimeEntry, Enumeration, Attachment, File, IssueJournal, WikiPage,
7+
ProjectMembership, IssueCategory, IssueRelation, Version, User, Group, Role, News,
8+
IssueStatus, Tracker, Query, CustomField)

redminelib/resources/standard.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ class Project(BaseResource):
2626

2727
_repr = [['id', 'name'], ['title']]
2828
_includes = ['trackers', 'issue_categories', 'enabled_modules', 'time_entry_activities']
29-
_relations = ['wiki_pages', 'memberships', 'issue_categories', 'time_entries', 'versions', 'news', 'issues']
29+
_relations = ['wiki_pages', 'memberships', 'issue_categories', 'time_entries', 'versions',
30+
'news', 'issues', 'files']
3031
_unconvertible = BaseResource._unconvertible + ['identifier', 'status']
3132
_update_readonly = BaseResource._update_readonly + ['identifier']
3233
_resource_set_map = {
@@ -39,6 +40,7 @@ class Project(BaseResource):
3940
'versions': 'Version',
4041
'news': 'News',
4142
'issues': 'Issue',
43+
'files': 'File',
4244
}
4345
_single_attr_id_map = {'parent_id': 'parent'}
4446
_multiple_attr_id_map = {'tracker_ids': 'trackers'}
@@ -223,6 +225,24 @@ def download(self, savepath=None, filename=None):
223225
return self.manager.redmine.download(self.content_url, savepath, filename)
224226

225227

228+
class File(Attachment):
229+
redmine_version = '3.4'
230+
container_filter = 'files'
231+
container_create = 'file'
232+
query_filter = '/projects/{project_id}/files.json'
233+
query_create = '/projects/{project_id}/files.json'
234+
manager_class = managers.FileManager
235+
236+
_resource_map = {'author': 'User', 'version': 'Version'}
237+
238+
@classmethod
239+
def decode(cls, attr, value, manager):
240+
if attr == 'path':
241+
return 'token', manager.redmine.upload(value)['token']
242+
243+
return super(File, cls).decode(attr, value, manager)
244+
245+
226246
class IssueJournal(BaseResource):
227247
redmine_version = '1.0'
228248

tests/responses/standard.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
'attachment': {
2020
'get': {'attachment': {'filename': 'foo.jpg', 'id': 1}},
2121
},
22+
'file': {
23+
'filter': {'files': [{'filename': 'foo.jpg', 'id': 1}, {'filename': 'bar.jpg', 'id': 2}]},
24+
},
2225
'wiki_page': {
2326
'get': {'wiki_page': {'title': 'Foo', 'version': 1}},
2427
'filter': {'wiki_pages': [{'title': 'Foo', 'version': 1}, {'title': 'Bar', 'version': 2}]},

0 commit comments

Comments
 (0)