Skip to content

Commit 174185b

Browse files
author
Gauvain Pocentek
committed
Add support for user avatar upload
Fixes #308
1 parent 175abe9 commit 174185b

File tree

7 files changed

+76
-20
lines changed

7 files changed

+76
-20
lines changed

docs/gl_objects/users.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ Block/Unblock a user::
6161
user.block()
6262
user.unblock()
6363

64+
Set the avatar image for a user::
65+
66+
# the avatar image can be passed as data (content of the file) or as a file
67+
# object opened in binary mode
68+
user.avatar = open('path/to/file.png', 'rb')
69+
user.save()
70+
6471
User custom attributes
6572
======================
6673

gitlab/__init__.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -356,20 +356,25 @@ def copy_dict(dest, src):
356356

357357
opts = self._get_session_opts(content_type='application/json')
358358

359-
# don't set the content-type header when uploading files
360-
if files is not None:
361-
del opts["headers"]["Content-type"]
362-
363359
verify = opts.pop('verify')
364360
timeout = opts.pop('timeout')
365361

362+
# We need to deal with json vs. data when uploading files
363+
if files:
364+
data = post_data
365+
json = None
366+
del opts["headers"]["Content-type"]
367+
else:
368+
json = post_data
369+
data = None
370+
366371
# Requests assumes that `.` should not be encoded as %2E and will make
367372
# changes to urls using this encoding. Using a prepped request we can
368373
# get the desired behavior.
369374
# The Requests behavior is right but it seems that web servers don't
370375
# always agree with this decision (this is the case with a default
371376
# gitlab installation)
372-
req = requests.Request(verb, url, json=post_data, params=params,
377+
req = requests.Request(verb, url, json=json, data=data, params=params,
373378
files=files, **opts)
374379
prepped = self.session.prepare_request(req)
375380
prepped.url = sanitized_url(prepped.url)
@@ -506,7 +511,8 @@ def http_post(self, path, query_data={}, post_data={}, files=None,
506511
error_message="Failed to parse the server message")
507512
return result
508513

509-
def http_put(self, path, query_data={}, post_data={}, **kwargs):
514+
def http_put(self, path, query_data={}, post_data={}, files=None,
515+
**kwargs):
510516
"""Make a PUT request to the Gitlab server.
511517
512518
Args:
@@ -515,6 +521,7 @@ def http_put(self, path, query_data={}, post_data={}, **kwargs):
515521
query_data (dict): Data to send as query parameters
516522
post_data (dict): Data to send in the body (will be converted to
517523
json)
524+
files (dict): The files to send to the server
518525
**kwargs: Extra data to make the query (e.g. sudo, per_page, page)
519526
520527
Returns:
@@ -525,7 +532,7 @@ def http_put(self, path, query_data={}, post_data={}, **kwargs):
525532
GitlabParsingError: If the json data could not be parsed
526533
"""
527534
result = self.http_request('put', path, query_data=query_data,
528-
post_data=post_data, **kwargs)
535+
post_data=post_data, files=files, **kwargs)
529536
try:
530537
return result.json()
531538
except Exception:

gitlab/mixins.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import gitlab
1919
from gitlab import base
2020
from gitlab import cli
21+
from gitlab import types as g_types
2122
from gitlab import exceptions as exc
2223

2324

@@ -171,21 +172,29 @@ def create(self, data, **kwargs):
171172
GitlabCreateError: If the server cannot perform the request
172173
"""
173174
self._check_missing_create_attrs(data)
175+
files = {}
174176

175177
# We get the attributes that need some special transformation
176178
types = getattr(self, '_types', {})
177-
178179
if types:
179180
# Duplicate data to avoid messing with what the user sent us
180181
data = data.copy()
181182
for attr_name, type_cls in types.items():
182183
if attr_name in data.keys():
183184
type_obj = type_cls(data[attr_name])
184-
data[attr_name] = type_obj.get_for_api()
185+
186+
# if the type if FileAttribute we need to pass the data as
187+
# file
188+
if issubclass(type_cls, g_types.FileAttribute):
189+
k = type_obj.get_file_name(attr_name)
190+
files[attr_name] = (k, data.pop(attr_name))
191+
else:
192+
data[attr_name] = type_obj.get_for_api()
185193

186194
# Handle specific URL for creation
187195
path = kwargs.pop('path', self.path)
188-
server_data = self.gitlab.http_post(path, post_data=data, **kwargs)
196+
server_data = self.gitlab.http_post(path, post_data=data, files=files,
197+
**kwargs)
189198
return self._obj_cls(self, server_data)
190199

191200

@@ -232,15 +241,27 @@ def update(self, id=None, new_data={}, **kwargs):
232241
path = '%s/%s' % (self.path, id)
233242

234243
self._check_missing_update_attrs(new_data)
244+
files = {}
235245

236246
# We get the attributes that need some special transformation
237247
types = getattr(self, '_types', {})
238-
for attr_name, type_cls in types.items():
239-
if attr_name in new_data.keys():
240-
type_obj = type_cls(new_data[attr_name])
241-
new_data[attr_name] = type_obj.get_for_api()
242-
243-
return self.gitlab.http_put(path, post_data=new_data, **kwargs)
248+
if types:
249+
# Duplicate data to avoid messing with what the user sent us
250+
new_data = new_data.copy()
251+
for attr_name, type_cls in types.items():
252+
if attr_name in new_data.keys():
253+
type_obj = type_cls(new_data[attr_name])
254+
255+
# if the type if FileAttribute we need to pass the data as
256+
# file
257+
if issubclass(type_cls, g_types.FileAttribute):
258+
k = type_obj.get_file_name(attr_name)
259+
files[attr_name] = (k, new_data.pop(attr_name))
260+
else:
261+
new_data[attr_name] = type_obj.get_for_api()
262+
263+
return self.gitlab.http_put(path, post_data=new_data, files=files,
264+
**kwargs)
244265

245266

246267
class SetMixin(object):

gitlab/types.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,13 @@ def get_for_api(self):
4444
class LowercaseStringAttribute(GitlabAttribute):
4545
def get_for_api(self):
4646
return str(self._value).lower()
47+
48+
49+
class FileAttribute(GitlabAttribute):
50+
def get_file_name(self, attr_name=None):
51+
return attr_name
52+
53+
54+
class ImageAttribute(FileAttribute):
55+
def get_file_name(self, attr_name=None):
56+
return '%s.png' % attr_name if attr_name else 'image.png'

gitlab/v4/objects.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -307,16 +307,19 @@ class UserManager(CRUDMixin, RESTManager):
307307
('email', 'username', 'name', 'password', 'reset_password', 'skype',
308308
'linkedin', 'twitter', 'projects_limit', 'extern_uid', 'provider',
309309
'bio', 'admin', 'can_create_group', 'website_url',
310-
'skip_confirmation', 'external', 'organization', 'location')
310+
'skip_confirmation', 'external', 'organization', 'location', 'avatar')
311311
)
312312
_update_attrs = (
313313
('email', 'username', 'name'),
314314
('password', 'skype', 'linkedin', 'twitter', 'projects_limit',
315315
'extern_uid', 'provider', 'bio', 'admin', 'can_create_group',
316316
'website_url', 'skip_confirmation', 'external', 'organization',
317-
'location')
317+
'location', 'avatar')
318318
)
319-
_types = {'confirm': types.LowercaseStringAttribute}
319+
_types = {
320+
'confirm': types.LowercaseStringAttribute,
321+
'avatar': types.ImageAttribute,
322+
}
320323

321324

322325
class CurrentUserEmail(ObjectDeleteMixin, RESTObject):

tools/avatar.png

592 Bytes
Loading

tools/python_test_v4.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import base64
2+
import os
23
import time
34

5+
import requests
6+
47
import gitlab
58

69
LOGIN = 'root'
@@ -49,6 +52,7 @@
4952
qG2ZdhHHmSK2LaQLFiSprUkikStNU9BqSQ==
5053
=5OGa
5154
-----END PGP PUBLIC KEY BLOCK-----'''
55+
AVATAR_PATH = os.path.join(os.path.dirname(__file__), 'avatar.png')
5256

5357

5458
# token authentication from config file
@@ -81,7 +85,11 @@
8185

8286
# users
8387
new_user = gl.users.create({'email': 'foo@bar.com', 'username': 'foo',
84-
'name': 'foo', 'password': 'foo_password'})
88+
'name': 'foo', 'password': 'foo_password',
89+
'avatar': open(AVATAR_PATH, 'rb')})
90+
avatar_url = new_user.avatar_url.replace('gitlab.test', 'localhost:8080')
91+
uploaded_avatar = requests.get(avatar_url).content
92+
assert(uploaded_avatar == open(AVATAR_PATH, 'rb').read())
8593
users_list = gl.users.list()
8694
for user in users_list:
8795
if user.username == 'foo':

0 commit comments

Comments
 (0)