Skip to content

Commit 700ee3a

Browse files
authored
reworked users.get_details (#145)
Changes proposed in this pull request: * `users.get_details` renamed to `get_user` and returns a class instead of a dictionary. * Optional argument `displayname` in `users.create` renamed to `display_name`. ## Fixed: * `users.get_details` with empty parameter in some cases was raised exception. --------- Signed-off-by: Alexander Piskun <bigcat88@icloud.com>
1 parent 3c189b6 commit 700ee3a

File tree

8 files changed

+245
-44
lines changed

8 files changed

+245
-44
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [0.4.0 - 2023-10-2x]
6+
7+
This release contains some breaking changes in `users` API.
8+
9+
### Changed
10+
11+
- `users.get_details` renamed to `get_user` and returns a class instead of a dictionary.
12+
- Optional argument `displayname` in `users.create` renamed to `display_name`.
13+
14+
### Fixed
15+
16+
- `users.get_details` with empty parameter in some cases was raised exception.
17+
518
## [0.3.1 - 2023-10-07]
619

720
### Added

docs/reference/Users/Users.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
User Management
22
---------------
33

4+
.. autoclass:: nc_py_api.users.UserInfo
5+
:members:
6+
47
.. autoclass:: nc_py_api.users._UsersAPI
58
:members:

nc_py_api/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Version of nc_py_api."""
22

3-
__version__ = "0.3.1"
3+
__version__ = "0.4.0.dev0"

nc_py_api/users.py

Lines changed: 156 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,161 @@
11
"""Nextcloud API for working with users."""
22

3+
import dataclasses
4+
import datetime
35
import typing
46

57
from ._misc import kwargs_to_params
68
from ._session import NcSessionBasic
79

810

11+
@dataclasses.dataclass
12+
class UserInfo:
13+
"""User information description."""
14+
15+
def __init__(self, raw_data: dict):
16+
self._raw_data = raw_data
17+
18+
@property
19+
def enabled(self) -> bool:
20+
"""Flag indicating whether the user is enabled."""
21+
return self._raw_data.get("enabled", True)
22+
23+
@property
24+
def storage_location(self) -> str:
25+
"""User's home folder. Can be empty for LDAP or when the caller does not have administrative rights."""
26+
return self._raw_data.get("storageLocation", "")
27+
28+
@property
29+
def user_id(self) -> str:
30+
"""User ID."""
31+
return self._raw_data["id"]
32+
33+
@property
34+
def last_login(self) -> datetime.datetime:
35+
"""Last user login time."""
36+
return datetime.datetime.utcfromtimestamp(int(self._raw_data["lastLogin"] / 1000)).replace(
37+
tzinfo=datetime.timezone.utc
38+
)
39+
40+
@property
41+
def backend(self) -> str:
42+
"""The type of backend in which user information is stored."""
43+
return self._raw_data["backend"]
44+
45+
@property
46+
def subadmin(self) -> list[str]:
47+
"""IDs of groups of which the user is a subadmin."""
48+
return self._raw_data.get("subadmin", [])
49+
50+
@property
51+
def quota(self) -> dict:
52+
"""Quota for the user, if set."""
53+
return self._raw_data["quota"] if isinstance(self._raw_data["quota"], dict) else {}
54+
55+
@property
56+
def manager(self) -> str:
57+
"""The user's manager UID."""
58+
return self._raw_data.get("manager", "")
59+
60+
@property
61+
def email(self) -> str:
62+
"""Email address of the user."""
63+
return self._raw_data["email"] if self._raw_data["email"] is not None else ""
64+
65+
@property
66+
def additional_mail(self) -> list[str]:
67+
"""List of additional emails."""
68+
return self._raw_data["additional_mail"]
69+
70+
@property
71+
def display_name(self) -> str:
72+
"""The display name of the new user."""
73+
return self._raw_data["displayname"] if "displayname" in self._raw_data else self._raw_data["display-name"]
74+
75+
@property
76+
def phone(self) -> str:
77+
"""Phone of the user."""
78+
return self._raw_data["phone"]
79+
80+
@property
81+
def address(self) -> str:
82+
"""Address of the user."""
83+
return self._raw_data["address"]
84+
85+
@property
86+
def website(self) -> str:
87+
"""Link to user website."""
88+
return self._raw_data["website"]
89+
90+
@property
91+
def twitter(self) -> str:
92+
"""Twitter handle."""
93+
return self._raw_data["twitter"]
94+
95+
@property
96+
def fediverse(self) -> str:
97+
"""Fediverse(e.g. Mastodon) in the user profile."""
98+
return self._raw_data["fediverse"]
99+
100+
@property
101+
def organisation(self) -> str:
102+
"""Organisation in the user profile."""
103+
return self._raw_data["organisation"]
104+
105+
@property
106+
def role(self) -> str:
107+
"""Role in the user profile."""
108+
return self._raw_data["role"]
109+
110+
@property
111+
def headline(self) -> str:
112+
"""Headline in the user profile."""
113+
return self._raw_data["headline"]
114+
115+
@property
116+
def biography(self) -> str:
117+
"""Biography in the user profile."""
118+
return self._raw_data["biography"]
119+
120+
@property
121+
def profile_enabled(self) -> bool:
122+
"""Flag indicating whether the user profile is enabled."""
123+
return str(self._raw_data["profile_enabled"]).lower() in ("1", "true")
124+
125+
@property
126+
def groups(self) -> list[str]:
127+
"""ID of the groups the user is a member of."""
128+
return self._raw_data["groups"]
129+
130+
@property
131+
def language(self) -> str:
132+
"""The language to use when sending something to a user."""
133+
return self._raw_data["language"]
134+
135+
@property
136+
def locale(self) -> str:
137+
"""The locale set for the user."""
138+
return self._raw_data.get("locale", "")
139+
140+
@property
141+
def notify_email(self) -> str:
142+
"""The user's preferred email address.
143+
144+
.. note:: The primary mail address may be set be the user to specify a different
145+
email address where mails by Nextcloud are sent to. It is not necessarily set.
146+
"""
147+
return self._raw_data["notify_email"] if self._raw_data["notify_email"] is not None else ""
148+
149+
@property
150+
def backend_capabilities(self) -> dict:
151+
"""By default, only the ``setDisplayName`` and ``setPassword`` keys are available."""
152+
return self._raw_data["backendCapabilities"]
153+
154+
9155
class _UsersAPI:
10-
"""The class provides the user, user groups, user status API on the Nextcloud server.
156+
"""The class provides the user API on the Nextcloud server.
11157
12-
.. note:: In NextcloudApp mode, only ``get_list`` and ``get_details`` methods are available.
158+
.. note:: In NextcloudApp mode, only ``get_list``, ``editable_fields`` and ``get_user`` methods are available.
13159
"""
14160

15161
_ep_base: str = "/ocs/v1.php/cloud/users"
@@ -30,28 +176,25 @@ def get_list(
30176
response_data = self._session.ocs(method="GET", path=self._ep_base, params=data)
31177
return response_data["users"] if response_data else {}
32178

33-
def get_details(self, user_id: str = "") -> dict:
179+
def get_user(self, user_id: str = "") -> UserInfo:
34180
"""Returns detailed user information.
35181
36182
:param user_id: the identifier of the user about which information is to be returned.
37183
"""
38-
if not user_id:
39-
user_id = self._session.user
40-
if not user_id:
41-
raise ValueError("user_id can not be empty.")
42-
return self._session.ocs(method="GET", path=f"{self._ep_base}/{user_id}")
184+
url_path = f"{self._ep_base}/{user_id}" if user_id else "/ocs/v1.php/cloud/user"
185+
return UserInfo(self._session.ocs(method="GET", path=url_path))
43186

44-
def create(self, user_id: str, **kwargs) -> None:
187+
def create(self, user_id: str, display_name: typing.Optional[str] = None, **kwargs) -> None:
45188
"""Create a new user on the Nextcloud server.
46189
47190
:param user_id: id of the user to create.
191+
:param display_name: display name for a created user.
48192
:param kwargs: See below.
49193
50194
Additionally supported arguments:
51195
52196
* ``password`` - password that should be set for user.
53197
* ``email`` - email of the new user. If ``password`` is not provided, then this field should be filled.
54-
* ``displayname`` - display name of the new user.
55198
* ``groups`` - list of groups IDs to which user belongs.
56199
* ``subadmin`` - boolean indicating is user should be the subadmin.
57200
* ``quota`` - quota for the user, if needed.
@@ -62,9 +205,11 @@ def create(self, user_id: str, **kwargs) -> None:
62205
if not password and not email:
63206
raise ValueError("Either password or email must be set")
64207
data = {"userid": user_id}
65-
for k in ("password", "displayname", "email", "groups", "subadmin", "quota", "language"):
208+
for k in ("password", "email", "groups", "subadmin", "quota", "language"):
66209
if k in kwargs:
67210
data[k] = kwargs[k]
211+
if display_name is not None:
212+
data["displayname"] = display_name
68213
self._session.ocs(method="POST", path=self._ep_base, json=data)
69214

70215
def delete(self, user_id: str) -> None:

tests/actual_tests/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ def tear_up_down(nc_any, rand_bytes):
105105
environ["TEST_USER_ID"],
106106
password=environ["TEST_USER_PASS"],
107107
groups=[environ["TEST_GROUP_BOTH"], environ["TEST_GROUP_USER"]],
108+
display_name=environ["TEST_USER_ID"],
108109
)
109110
init_filesystem_for_user(nc_any, rand_bytes) # currently we initialize filesystem only for admin
110111

tests/actual_tests/misc_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def test_deffered_error():
6060

6161
def test_ocs_response_headers(nc):
6262
old_headers = nc.response_headers
63-
nc.users.get_details()
63+
nc.users.get_user()
6464
assert old_headers != nc.response_headers
6565

6666

0 commit comments

Comments
 (0)