udata 8.0.1.dev29075__py2.py3-none-any.whl → 8.0.1.dev29111__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of udata might be problematic. Click here for more details.

@@ -8,6 +8,7 @@ from udata import search
8
8
  from udata.api import apiv2, API, fields
9
9
  from udata.utils import multi_to_dict, get_by
10
10
 
11
+ from udata.core.organization.api_fields import member_user_with_email_fields
11
12
  from .api_fields import (
12
13
  badge_fields,
13
14
  org_ref_fields,
@@ -165,6 +166,7 @@ specific_resource_fields = apiv2.model('SpecificResource', {
165
166
  apiv2.inherit('Badge', badge_fields)
166
167
  apiv2.inherit('OrganizationReference', org_ref_fields)
167
168
  apiv2.inherit('UserReference', user_ref_fields)
169
+ apiv2.inherit('MemberUserWithEmail', member_user_with_email_fields)
168
170
  apiv2.inherit('Resource', resource_fields)
169
171
  apiv2.inherit('SpatialCoverage', spatial_coverage_fields)
170
172
  apiv2.inherit('TemporalCoverage', temporal_coverage_fields)
@@ -505,6 +505,7 @@ class Dataset(WithMetrics, BadgeMixin, Owned, db.Document):
505
505
  'reuses',
506
506
  'followers',
507
507
  'views',
508
+ 'resources_downloads',
508
509
  ]
509
510
 
510
511
  meta = {
@@ -1,5 +1,8 @@
1
+ from flask import request
2
+
1
3
  from udata.api import api, fields, base_reference
2
4
  from udata.core.badges.fields import badge_fields
5
+ from udata.core.organization.permissions import OrganizationPrivatePermission
3
6
 
4
7
  from .constants import ORG_ROLES, DEFAULT_ROLE, MEMBERSHIP_STATUS, BIGGEST_LOGO_SIZE
5
8
 
@@ -27,9 +30,29 @@ org_ref_fields = api.inherit('OrganizationReference', base_reference, {
27
30
 
28
31
  from udata.core.user.api_fields import user_ref_fields # noqa: required
29
32
 
33
+ def check_can_access_email():
34
+ # This endpoint is secure, only organization member has access.
35
+ if request.endpoint == 'api.request_membership':
36
+ return True
37
+
38
+ if request.endpoint != 'api.organization':
39
+ return False
40
+
41
+ org = request.view_args.get('org')
42
+ if org is None:
43
+ return False
44
+
45
+ return OrganizationPrivatePermission(org).can()
46
+
47
+ member_user_with_email_fields = api.inherit('MemberUserWithEmail', user_ref_fields, {
48
+ 'email': fields.Raw(
49
+ attribute=lambda o: o.email if check_can_access_email() else None,
50
+ description='The user email (only present on show organization endpoint if the current user has edit permission on the org)', readonly=True),
51
+ })
52
+
30
53
  request_fields = api.model('MembershipRequest', {
31
54
  'id': fields.String(readonly=True),
32
- 'user': fields.Nested(user_ref_fields),
55
+ 'user': fields.Nested(member_user_with_email_fields),
33
56
  'created': fields.ISODateTime(
34
57
  description='The request creation date', readonly=True),
35
58
  'status': fields.String(
@@ -40,10 +63,12 @@ request_fields = api.model('MembershipRequest', {
40
63
  })
41
64
 
42
65
  member_fields = api.model('Member', {
43
- 'user': fields.Nested(user_ref_fields),
66
+ 'user': fields.Nested(member_user_with_email_fields),
44
67
  'role': fields.String(
45
68
  description='The member role in the organization', required=True,
46
- enum=list(ORG_ROLES), default=DEFAULT_ROLE)
69
+ enum=list(ORG_ROLES), default=DEFAULT_ROLE),
70
+ 'since': fields.ISODateTime(
71
+ description='The date the user joined the organization', readonly=True),
47
72
  })
48
73
 
49
74
  org_fields = api.model('Organization', {
@@ -191,8 +191,7 @@ class MembershipAPITest:
191
191
  user = api.login()
192
192
  data = {'comment': 'a comment'}
193
193
 
194
- api_url = url_for('api.request_membership', org=organization)
195
- response = api.post(api_url, data)
194
+ response = api.post(url_for('api.request_membership', org=organization), data)
196
195
  assert201(response)
197
196
 
198
197
  organization.reload()
@@ -209,14 +208,13 @@ class MembershipAPITest:
209
208
  assert request.handled_by is None
210
209
  assert request.refusal_comment is None
211
210
 
212
- def test_request_existing_pending_membership(self, api):
211
+ def test_request_existing_pending_membership_do_not_duplicate_it(self, api):
213
212
  user = api.login()
214
213
  previous_request = MembershipRequest(user=user, comment='previous')
215
214
  organization = OrganizationFactory(requests=[previous_request])
216
215
  data = {'comment': 'a comment'}
217
216
 
218
- api_url = url_for('api.request_membership', org=organization)
219
- response = api.post(api_url, data)
217
+ response = api.post(url_for('api.request_membership', org=organization), data)
220
218
  assert200(response)
221
219
 
222
220
  organization.reload()
@@ -233,6 +231,81 @@ class MembershipAPITest:
233
231
  assert request.handled_by is None
234
232
  assert request.refusal_comment is None
235
233
 
234
+ def test_get_membership_requests(self, api):
235
+ user = api.login()
236
+ applicant = UserFactory(email="thibaud@example.org")
237
+ membership_request = MembershipRequest(user=applicant, comment='test')
238
+ member = Member(user=user, role='admin')
239
+ organization = OrganizationFactory(
240
+ members=[member], requests=[membership_request])
241
+
242
+ response = api.get(url_for('api.request_membership', org=organization))
243
+ assert200(response)
244
+
245
+ assert len(response.json) == 1
246
+ assert response.json[0]['comment'] == 'test'
247
+ assert response.json[0]['user']['email'] == 'thibaud@example.org' # Can see email of applicant
248
+
249
+ def test_only_org_member_can_get_membership_requests(self, api):
250
+ api.login()
251
+ applicant = UserFactory(email="thibaud@example.org")
252
+ membership_request = MembershipRequest(user=applicant, comment='test')
253
+ organization = OrganizationFactory(
254
+ members=[], requests=[membership_request])
255
+
256
+ response = api.get(url_for('api.request_membership', org=organization))
257
+ assert403(response)
258
+
259
+
260
+ def test_get_members_with_or_without_email(self, api):
261
+ admin = Member(user=UserFactory(email="admin@example.org"), role='admin', since="2024-04-14")
262
+ editor = Member(user=UserFactory(email="editor@example.org"), role='editor')
263
+ other = UserFactory(email="other@example.org")
264
+
265
+ organization = OrganizationFactory(members=[admin, editor])
266
+
267
+ # Admin can see emails
268
+ api.login(admin.user)
269
+ response = api.get(url_for('api.organization', org=organization))
270
+ assert200(response)
271
+
272
+ members = response.json['members']
273
+ assert len(members) == 2
274
+ assert members[0]['role'] == 'admin'
275
+ assert members[0]['since'] == '2024-04-14T00:00:00+00:00'
276
+ assert members[0]['user']['email'] == 'admin@example.org'
277
+
278
+ assert members[1]['role'] == 'editor'
279
+ assert members[1]['user']['email'] == 'editor@example.org'
280
+
281
+ # Editor can see emails
282
+ api.login(editor.user)
283
+ response = api.get(url_for('api.organization', org=organization))
284
+ assert200(response)
285
+
286
+ members = response.json['members']
287
+ assert len(members) == 2
288
+ assert members[0]['role'] == 'admin'
289
+ assert members[0]['since'] == '2024-04-14T00:00:00+00:00'
290
+ assert members[0]['user']['email'] == 'admin@example.org'
291
+
292
+ assert members[1]['role'] == 'editor'
293
+ assert members[1]['user']['email'] == 'editor@example.org'
294
+
295
+ # Others cannot see emails
296
+ api.login(other)
297
+ response = api.get(url_for('api.organization', org=organization))
298
+ assert200(response)
299
+
300
+ members = response.json['members']
301
+ assert len(members) == 2
302
+ assert members[0]['role'] == 'admin'
303
+ assert members[0]['since'] == '2024-04-14T00:00:00+00:00'
304
+ assert members[0]['user']['email'] is None
305
+
306
+ assert members[1]['role'] == 'editor'
307
+ assert members[1]['user']['email'] is None
308
+
236
309
  def test_accept_membership(self, api):
237
310
  user = api.login()
238
311
  applicant = UserFactory()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: udata
3
- Version: 8.0.1.dev29075
3
+ Version: 8.0.1.dev29111
4
4
  Summary: Open data portal
5
5
  Home-page: https://github.com/opendatateam/udata
6
6
  Author: Opendata Team
@@ -143,6 +143,8 @@ It is collectively taken care of by members of the
143
143
  - Delete a user without sending mail [#3031](https://github.com/opendatateam/udata/pull/3031)
144
144
  - Convert known HVD categories used as theme to keywords [#3014](https://github.com/opendatateam/udata/pull/3014)
145
145
  - Allow for series in CSW ISO 19139 DCAT backend [#3028](https://github.com/opendatateam/udata/pull/3028)
146
+ - Add `email` to membership request list API response, add `since` to org members API responses, add `email` to members of org on show org endpoint for org's admins and editors [#3038](https://github.com/opendatateam/udata/pull/3038)
147
+ - Add `resources_downloads` to datasets metrics [#3042](https://github.com/opendatateam/udata/pull/3042)
146
148
  - Fix do not override resources extras on admin during update [#3043](https://github.com/opendatateam/udata/pull/3043)
147
149
 
148
150
  ## 8.0.0 (2024-04-23)
@@ -86,7 +86,7 @@ udata/core/dataset/actions.py,sha256=3pzBg_qOR-w7fwPpTOKUHXWC9lkjALbOn1UQFmmT-s0
86
86
  udata/core/dataset/activities.py,sha256=qQnHNL0hOB1IGtQl7JsnVOiUsWT0gm-pts9uDyR3bvU,1536
87
87
  udata/core/dataset/api.py,sha256=C6EGhJswCb2hHfKigbxFQbfzpzgvWm8VD3c4O-ZQFso,28789
88
88
  udata/core/dataset/api_fields.py,sha256=Cgd_BPlAhP0nTzocOZWWEM-LvKcgCxYE2x00oAiKFYM,15405
89
- udata/core/dataset/apiv2.py,sha256=XzEwtmC53Mw0mq8NEMCD2cN52pCmrUkPu0adUj4wGyw,15751
89
+ udata/core/dataset/apiv2.py,sha256=NmfixLd7Q8WNEAKxpJWubD2QzsxgJwJo69z8kNtU__Y,15896
90
90
  udata/core/dataset/commands.py,sha256=dbK7gMYH4G6pOPiYtmL3yomzg5MGqTaG97_UM9Smu_k,3712
91
91
  udata/core/dataset/constants.py,sha256=Twml-I5mGiIdTkiDV1D4bXqi_TcNhcEc-QFxorUKHM4,3138
92
92
  udata/core/dataset/csv.py,sha256=d6JMuvlov_vR7EN10rJa6Q03Il0PfbzMTHQIud5H8qg,3240
@@ -94,7 +94,7 @@ udata/core/dataset/events.py,sha256=DI71VfRc1eDTtgWQ3TJx5gtUw2MO0O_CVLCKLq0OIF0,
94
94
  udata/core/dataset/exceptions.py,sha256=uI_NvZRZMr_MtYQBYdLD8tk-BIUeDDfMMcrWwqV7mi8,494
95
95
  udata/core/dataset/factories.py,sha256=fHPgEUUpqGw7p7K5wGg-wA6Q0-Pc9eNbzAQK-va8zMM,9120
96
96
  udata/core/dataset/forms.py,sha256=VJCsGtgzhQgLW-M-J4ObpQ8o6c_saC3TTc1Js33m3sQ,6188
97
- udata/core/dataset/models.py,sha256=Z9Sqf-Aly67BB5ESwz3H4qxJvG3zy03Goz6enL55HyE,36022
97
+ udata/core/dataset/models.py,sha256=q6AknSHYNjeUyVKoFgxEA0r9lSvMa_a4tJ3fLOzL03M,36053
98
98
  udata/core/dataset/permissions.py,sha256=3F2J7le3_rEYNhh88o3hSRWHAAt01_yHJM6RPmvCrRo,1090
99
99
  udata/core/dataset/preview.py,sha256=puPKT3fBD7ezAcT6owh0JK1_rGNDFZOqgT223qGn3LY,2597
100
100
  udata/core/dataset/rdf.py,sha256=y4XBB8nlm_xmqwWGrtiRJj3UZT8DtzCREw4jchOaGsg,23731
@@ -133,7 +133,7 @@ udata/core/metrics/tasks.py,sha256=Z7La3-zPB5j6qJoPKr_MgjNZhqscZCmNLPa711ZBkdY,7
133
133
  udata/core/organization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
134
134
  udata/core/organization/activities.py,sha256=fOBujmduVoTxJph2C6Z4qHeQHOaJT9IsZooEDCWC4BA,1140
135
135
  udata/core/organization/api.py,sha256=K9Lnps_gxxt_z1ImLZDBnY1eeh6Zpl7x50YoXcPzJ_k,17479
136
- udata/core/organization/api_fields.py,sha256=UV3DE7DoWvrRs2k-VnuJ2c7-mMTt93eVX8o-K_9IvXQ,5403
136
+ udata/core/organization/api_fields.py,sha256=Xotv1Eqd3fnxhlC0cssSEG4mfiRfLKPQK_k07V1hmDc,6370
137
137
  udata/core/organization/apiv2.py,sha256=VAU_y9Zz-VhBgS-LWVbGOEZdSt3b44nZd5bzTV2wU8g,3206
138
138
  udata/core/organization/commands.py,sha256=FaSYxyWfQDR5tWvrAXmwcF2VMREOC13XTK8DD4vp_sY,1623
139
139
  udata/core/organization/constants.py,sha256=XGVnItrJTG0hcVKRK5sPmx44KHC9r_EKtj_y0MmBt0o,563
@@ -592,7 +592,7 @@ udata/tests/api/test_datasets_api.py,sha256=zqRtC61NvYUNum4kBTjA1W87JAIobA4k564r
592
592
  udata/tests/api/test_fields.py,sha256=OW85Z5MES5HeWOpapeem8OvR1cIcrqW-xMWpdZO4LZ8,1033
593
593
  udata/tests/api/test_follow_api.py,sha256=0h54P_Dfbo07u6tg0Rbai1WWgWb19ZLN2HGv4oLCWfg,3383
594
594
  udata/tests/api/test_me_api.py,sha256=8OthqVYQKZrFoGuJ7zAvoLJx1IclPNzPdD5Tnzmh3rM,14163
595
- udata/tests/api/test_organizations_api.py,sha256=HxeTqP14wlWGaQt4fpQljuRy98HWnmL1QelcxbJDmV0,32704
595
+ udata/tests/api/test_organizations_api.py,sha256=buE1KdGIoJxfsj-ofz5CkMtgnARi38bXWR1jgPRkMzo,35732
596
596
  udata/tests/api/test_reuses_api.py,sha256=GSoZ6SBRdMg6o0xKSQWtCSfvzqz_vZxERU567Rq1_GY,17009
597
597
  udata/tests/api/test_swagger.py,sha256=tLg452rCD5Q0AgFXOVZKMM6jGiWwC5diX5PxbdYni0o,760
598
598
  udata/tests/api/test_tags_api.py,sha256=xSIx_x5gqKz6BJCXKEsTqA_CR4BF1nG5NxEyfp9dy0o,2579
@@ -681,9 +681,9 @@ udata/translations/pt/LC_MESSAGES/udata.mo,sha256=uttB2K8VsqzkEQG-5HfTtFms_3LtV9
681
681
  udata/translations/pt/LC_MESSAGES/udata.po,sha256=8Ql1Lp7Z9KLnvp-qRxw-NhFu1p35Xj-q6Jg9JHsYhcw,43733
682
682
  udata/translations/sr/LC_MESSAGES/udata.mo,sha256=US8beNIMPxP5h-zD_jfP1TheDDd4DdRVS5UIiY5XVZ8,28553
683
683
  udata/translations/sr/LC_MESSAGES/udata.po,sha256=TM0yMDvKRljyOzgZZMlTX6OfpF6OC4Ngf_9Zc8n6ayA,50313
684
- udata-8.0.1.dev29075.dist-info/LICENSE,sha256=V8j_M8nAz8PvAOZQocyRDX7keai8UJ9skgmnwqETmdY,34520
685
- udata-8.0.1.dev29075.dist-info/METADATA,sha256=AnoJN4HbSzM1xVKvqFAVB6Q8vEk4z1ZjQ9i97S5_cCI,122815
686
- udata-8.0.1.dev29075.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
687
- udata-8.0.1.dev29075.dist-info/entry_points.txt,sha256=3SKiqVy4HUqxf6iWspgMqH8d88Htk6KoLbG1BU-UddQ,451
688
- udata-8.0.1.dev29075.dist-info/top_level.txt,sha256=39OCg-VWFWOq4gCKnjKNu-s3OwFlZIu_dVH8Gl6ndHw,12
689
- udata-8.0.1.dev29075.dist-info/RECORD,,
684
+ udata-8.0.1.dev29111.dist-info/LICENSE,sha256=V8j_M8nAz8PvAOZQocyRDX7keai8UJ9skgmnwqETmdY,34520
685
+ udata-8.0.1.dev29111.dist-info/METADATA,sha256=8XbLmQfd-YkSgFDbbzJshQ6nlUUnYZ9btLKZfcCfteI,123154
686
+ udata-8.0.1.dev29111.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
687
+ udata-8.0.1.dev29111.dist-info/entry_points.txt,sha256=3SKiqVy4HUqxf6iWspgMqH8d88Htk6KoLbG1BU-UddQ,451
688
+ udata-8.0.1.dev29111.dist-info/top_level.txt,sha256=39OCg-VWFWOq4gCKnjKNu-s3OwFlZIu_dVH8Gl6ndHw,12
689
+ udata-8.0.1.dev29111.dist-info/RECORD,,