summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/gl_objects/users.rst7
-rw-r--r--gitlab/__init__.py21
-rw-r--r--gitlab/mixins.py39
-rw-r--r--gitlab/types.py10
-rw-r--r--gitlab/v4/objects.py9
-rw-r--r--tools/avatar.pngbin0 -> 592 bytes
-rw-r--r--tools/python_test_v4.py10
7 files changed, 76 insertions, 20 deletions
diff --git a/docs/gl_objects/users.rst b/docs/gl_objects/users.rst
index a1d6dd6..fa966d1 100644
--- a/docs/gl_objects/users.rst
+++ b/docs/gl_objects/users.rst
@@ -61,6 +61,13 @@ Block/Unblock a user::
user.block()
user.unblock()
+Set the avatar image for a user::
+
+ # the avatar image can be passed as data (content of the file) or as a file
+ # object opened in binary mode
+ user.avatar = open('path/to/file.png', 'rb')
+ user.save()
+
User custom attributes
======================
diff --git a/gitlab/__init__.py b/gitlab/__init__.py
index af38680..3a36bf2 100644
--- a/gitlab/__init__.py
+++ b/gitlab/__init__.py
@@ -356,20 +356,25 @@ class Gitlab(object):
opts = self._get_session_opts(content_type='application/json')
- # don't set the content-type header when uploading files
- if files is not None:
- del opts["headers"]["Content-type"]
-
verify = opts.pop('verify')
timeout = opts.pop('timeout')
+ # We need to deal with json vs. data when uploading files
+ if files:
+ data = post_data
+ json = None
+ del opts["headers"]["Content-type"]
+ else:
+ json = post_data
+ data = None
+
# Requests assumes that `.` should not be encoded as %2E and will make
# changes to urls using this encoding. Using a prepped request we can
# get the desired behavior.
# The Requests behavior is right but it seems that web servers don't
# always agree with this decision (this is the case with a default
# gitlab installation)
- req = requests.Request(verb, url, json=post_data, params=params,
+ req = requests.Request(verb, url, json=json, data=data, params=params,
files=files, **opts)
prepped = self.session.prepare_request(req)
prepped.url = sanitized_url(prepped.url)
@@ -506,7 +511,8 @@ class Gitlab(object):
error_message="Failed to parse the server message")
return result
- def http_put(self, path, query_data={}, post_data={}, **kwargs):
+ def http_put(self, path, query_data={}, post_data={}, files=None,
+ **kwargs):
"""Make a PUT request to the Gitlab server.
Args:
@@ -515,6 +521,7 @@ class Gitlab(object):
query_data (dict): Data to send as query parameters
post_data (dict): Data to send in the body (will be converted to
json)
+ files (dict): The files to send to the server
**kwargs: Extra data to make the query (e.g. sudo, per_page, page)
Returns:
@@ -525,7 +532,7 @@ class Gitlab(object):
GitlabParsingError: If the json data could not be parsed
"""
result = self.http_request('put', path, query_data=query_data,
- post_data=post_data, **kwargs)
+ post_data=post_data, files=files, **kwargs)
try:
return result.json()
except Exception:
diff --git a/gitlab/mixins.py b/gitlab/mixins.py
index f940d60..17f1196 100644
--- a/gitlab/mixins.py
+++ b/gitlab/mixins.py
@@ -18,6 +18,7 @@
import gitlab
from gitlab import base
from gitlab import cli
+from gitlab import types as g_types
from gitlab import exceptions as exc
@@ -171,21 +172,29 @@ class CreateMixin(object):
GitlabCreateError: If the server cannot perform the request
"""
self._check_missing_create_attrs(data)
+ files = {}
# We get the attributes that need some special transformation
types = getattr(self, '_types', {})
-
if types:
# Duplicate data to avoid messing with what the user sent us
data = data.copy()
for attr_name, type_cls in types.items():
if attr_name in data.keys():
type_obj = type_cls(data[attr_name])
- data[attr_name] = type_obj.get_for_api()
+
+ # if the type if FileAttribute we need to pass the data as
+ # file
+ if issubclass(type_cls, g_types.FileAttribute):
+ k = type_obj.get_file_name(attr_name)
+ files[attr_name] = (k, data.pop(attr_name))
+ else:
+ data[attr_name] = type_obj.get_for_api()
# Handle specific URL for creation
path = kwargs.pop('path', self.path)
- server_data = self.gitlab.http_post(path, post_data=data, **kwargs)
+ server_data = self.gitlab.http_post(path, post_data=data, files=files,
+ **kwargs)
return self._obj_cls(self, server_data)
@@ -232,15 +241,27 @@ class UpdateMixin(object):
path = '%s/%s' % (self.path, id)
self._check_missing_update_attrs(new_data)
+ files = {}
# We get the attributes that need some special transformation
types = getattr(self, '_types', {})
- for attr_name, type_cls in types.items():
- if attr_name in new_data.keys():
- type_obj = type_cls(new_data[attr_name])
- new_data[attr_name] = type_obj.get_for_api()
-
- return self.gitlab.http_put(path, post_data=new_data, **kwargs)
+ if types:
+ # Duplicate data to avoid messing with what the user sent us
+ new_data = new_data.copy()
+ for attr_name, type_cls in types.items():
+ if attr_name in new_data.keys():
+ type_obj = type_cls(new_data[attr_name])
+
+ # if the type if FileAttribute we need to pass the data as
+ # file
+ if issubclass(type_cls, g_types.FileAttribute):
+ k = type_obj.get_file_name(attr_name)
+ files[attr_name] = (k, new_data.pop(attr_name))
+ else:
+ new_data[attr_name] = type_obj.get_for_api()
+
+ return self.gitlab.http_put(path, post_data=new_data, files=files,
+ **kwargs)
class SetMixin(object):
diff --git a/gitlab/types.py b/gitlab/types.py
index d361222..b32409f 100644
--- a/gitlab/types.py
+++ b/gitlab/types.py
@@ -44,3 +44,13 @@ class ListAttribute(GitlabAttribute):
class LowercaseStringAttribute(GitlabAttribute):
def get_for_api(self):
return str(self._value).lower()
+
+
+class FileAttribute(GitlabAttribute):
+ def get_file_name(self, attr_name=None):
+ return attr_name
+
+
+class ImageAttribute(FileAttribute):
+ def get_file_name(self, attr_name=None):
+ return '%s.png' % attr_name if attr_name else 'image.png'
diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py
index 14bad5a..2d9a6bf 100644
--- a/gitlab/v4/objects.py
+++ b/gitlab/v4/objects.py
@@ -307,16 +307,19 @@ class UserManager(CRUDMixin, RESTManager):
('email', 'username', 'name', 'password', 'reset_password', 'skype',
'linkedin', 'twitter', 'projects_limit', 'extern_uid', 'provider',
'bio', 'admin', 'can_create_group', 'website_url',
- 'skip_confirmation', 'external', 'organization', 'location')
+ 'skip_confirmation', 'external', 'organization', 'location', 'avatar')
)
_update_attrs = (
('email', 'username', 'name'),
('password', 'skype', 'linkedin', 'twitter', 'projects_limit',
'extern_uid', 'provider', 'bio', 'admin', 'can_create_group',
'website_url', 'skip_confirmation', 'external', 'organization',
- 'location')
+ 'location', 'avatar')
)
- _types = {'confirm': types.LowercaseStringAttribute}
+ _types = {
+ 'confirm': types.LowercaseStringAttribute,
+ 'avatar': types.ImageAttribute,
+ }
class CurrentUserEmail(ObjectDeleteMixin, RESTObject):
diff --git a/tools/avatar.png b/tools/avatar.png
new file mode 100644
index 0000000..a3a767c
--- /dev/null
+++ b/tools/avatar.png
Binary files differ
diff --git a/tools/python_test_v4.py b/tools/python_test_v4.py
index 62e1499..c11e567 100644
--- a/tools/python_test_v4.py
+++ b/tools/python_test_v4.py
@@ -1,6 +1,9 @@
import base64
+import os
import time
+import requests
+
import gitlab
LOGIN = 'root'
@@ -49,6 +52,7 @@ nxs4TLO3kZjUTgWKdhpgRNF5hwaz51ZjpebaRf/ZqRuNyX4lIRolDxzOn/+O1o8L
qG2ZdhHHmSK2LaQLFiSprUkikStNU9BqSQ==
=5OGa
-----END PGP PUBLIC KEY BLOCK-----'''
+AVATAR_PATH = os.path.join(os.path.dirname(__file__), 'avatar.png')
# token authentication from config file
@@ -81,7 +85,11 @@ assert(settings.default_projects_limit == 42)
# users
new_user = gl.users.create({'email': 'foo@bar.com', 'username': 'foo',
- 'name': 'foo', 'password': 'foo_password'})
+ 'name': 'foo', 'password': 'foo_password',
+ 'avatar': open(AVATAR_PATH, 'rb')})
+avatar_url = new_user.avatar_url.replace('gitlab.test', 'localhost:8080')
+uploaded_avatar = requests.get(avatar_url).content
+assert(uploaded_avatar == open(AVATAR_PATH, 'rb').read())
users_list = gl.users.list()
for user in users_list:
if user.username == 'foo':