udata 9.0.1.dev29632__py2.py3-none-any.whl → 9.0.1.dev29687__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.

udata/api/__init__.py CHANGED
@@ -323,6 +323,7 @@ def init_app(app):
323
323
  import udata.core.activity.api # noqa
324
324
  import udata.core.spatial.api # noqa
325
325
  import udata.core.user.api # noqa
326
+ import udata.core.user.apiv2 # noqa
326
327
  import udata.core.dataset.api # noqa
327
328
  import udata.core.dataset.apiv2 # noqa
328
329
  import udata.core.dataservices.api # noqa
@@ -1,6 +1,8 @@
1
1
  import logging
2
2
  from datetime import datetime
3
3
 
4
+ from flask_login import current_user
5
+
4
6
  from udata.mongo import db
5
7
  from udata.core.spam.models import SpamMixin, spam_protected
6
8
  from .signals import (on_new_discussion, on_discussion_closed, on_new_discussion_comment)
@@ -67,6 +69,24 @@ class Discussion(SpamMixin, db.Document):
67
69
  def embeds_to_check_for_spam(self):
68
70
  return self.discussion[1:]
69
71
 
72
+ def spam_is_whitelisted(self) -> bool:
73
+ from udata.core.dataset.permissions import OwnablePermission
74
+ from udata.core.owned import Owned
75
+
76
+ if not current_user.is_authenticated:
77
+ return False
78
+
79
+ if not isinstance(self.subject, Owned):
80
+ return False
81
+
82
+ # When creating a new Discussion the `subject` is an empty model
83
+ # with only `id`. We need to fetch it from the database to have
84
+ # all the required information
85
+ if not self.subject.owner or not self.subject.organization:
86
+ self.subject.reload()
87
+
88
+ return OwnablePermission(self.subject).can()
89
+
70
90
  @property
71
91
  def external_url(self):
72
92
  return self.subject.url_for(
udata/core/spam/models.py CHANGED
@@ -67,6 +67,9 @@ class SpamMixin(object):
67
67
  if not self.spam:
68
68
  self.spam = SpamInfo(status=NOT_CHECKED, callbacks={})
69
69
 
70
+ if self.spam_is_whitelisted():
71
+ return
72
+
70
73
  # The breadcrumb is useful during reporting to know where we came from
71
74
  # in case of a potential spam inside an embed.
72
75
  if breadcrumb is None:
@@ -139,6 +142,9 @@ class SpamMixin(object):
139
142
  def embeds_to_check_for_spam(self):
140
143
  return []
141
144
 
145
+ def spam_is_whitelisted(self) -> bool :
146
+ return False
147
+
142
148
  def spam_report_message(self):
143
149
  return f"Spam potentiel sur {type(self).__name__}"
144
150
 
@@ -3,7 +3,7 @@ from mongoengine.signals import pre_save
3
3
  from udata.models import db, SpatialCoverage
4
4
  from udata.search import reindex
5
5
  from udata.tasks import as_task_param
6
- from udata.core.owned import Owned
6
+ from udata.core.owned import Owned, OwnedQuerySet
7
7
 
8
8
 
9
9
  __all__ = ('Topic', )
@@ -36,7 +36,8 @@ class Topic(db.Document, Owned, db.Datetimed):
36
36
  'slug'
37
37
  ] + Owned.meta['indexes'],
38
38
  'ordering': ['-created_at'],
39
- 'auto_create_index_on_save': True
39
+ 'auto_create_index_on_save': True,
40
+ 'queryset_class': OwnedQuerySet,
40
41
  }
41
42
 
42
43
  def __str__(self):
@@ -11,10 +11,11 @@ class TopicApiParser(ModelApiParser):
11
11
  'last_modified': 'last_modified',
12
12
  }
13
13
 
14
- def __init__(self):
14
+ def __init__(self, with_include_private=True):
15
15
  super().__init__()
16
+ if with_include_private:
17
+ self.parser.add_argument('include_private', type=bool, location='args')
16
18
  self.parser.add_argument('tag', type=str, location='args')
17
- self.parser.add_argument('include_private', type=bool, location='args')
18
19
  self.parser.add_argument('geozone', type=str, location='args')
19
20
  self.parser.add_argument('granularity', type=str, location='args')
20
21
  self.parser.add_argument('organization', type=str, location='args')
@@ -0,0 +1,28 @@
1
+ from flask_security import current_user
2
+
3
+ from udata.api import apiv2, API
4
+ from udata.core.topic.apiv2 import topic_page_fields
5
+ from udata.core.topic.parsers import TopicApiParser
6
+ from udata.models import Topic
7
+
8
+ me = apiv2.namespace('me', 'Connected user related operations (v2)')
9
+
10
+ # we will force include_private to True, no need for this arg
11
+ topic_parser = TopicApiParser(with_include_private=False)
12
+
13
+
14
+ @me.route('/org_topics/', endpoint='my_org_topics')
15
+ class MyOrgTopicsAPI(API):
16
+ @apiv2.secure
17
+ @apiv2.doc('my_org_topics')
18
+ @apiv2.expect(topic_parser.parser)
19
+ @apiv2.marshal_list_with(topic_page_fields)
20
+ def get(self):
21
+ '''List all topics related to me and my organizations.'''
22
+ args = topic_parser.parse()
23
+ args["include_private"] = True
24
+ owners = list(current_user.organizations) + [current_user.id]
25
+ topics = Topic.objects.owned_by(*owners)
26
+ topics = topic_parser.parse_filters(topics, args)
27
+ sort = args['sort'] or ('$text_score' if args['q'] else None) or '-last-modified'
28
+ return topics.order_by(sort).paginate(args['page'], args['page_size'])
@@ -0,0 +1,40 @@
1
+ from flask import url_for
2
+
3
+ from udata.models import Member
4
+ from udata.core.organization.factories import OrganizationFactory
5
+ from udata.core.topic.factories import TopicFactory
6
+ from udata.tests.api import APITestCase
7
+
8
+
9
+ class MeAPIv2Test(APITestCase):
10
+ modules = []
11
+
12
+ def test_my_org_topics(self):
13
+ user = self.login()
14
+ member = Member(user=user, role='editor')
15
+ organization = OrganizationFactory(members=[member])
16
+ topics = [
17
+ TopicFactory(organization=organization, private=False, tags=['energy']),
18
+ TopicFactory(organization=organization, private=True),
19
+ TopicFactory(owner=user),
20
+ ]
21
+ # another topic that shouldn't pop up
22
+ TopicFactory()
23
+
24
+ response = self.get(url_for('apiv2.my_org_topics'))
25
+ assert response.status_code == 200
26
+ data = response.json['data']
27
+ assert len(data) == 3
28
+ assert all(
29
+ str(topic.id) in [remote_topic["id"] for remote_topic in data]
30
+ for topic in topics
31
+ )
32
+ assert 'rel' in data[0]['datasets']
33
+
34
+ # topic parser is already tested in topics test
35
+ # we're just making sure one of theme is working
36
+ response = self.get(url_for('apiv2.my_org_topics', tag='energy'))
37
+ assert response.status_code == 200
38
+ data = response.json['data']
39
+ assert len(data) == 1
40
+ assert data[0]['id'] == str(topics[0].id)
udata/tests/plugin.py CHANGED
@@ -8,6 +8,7 @@ from flask import json, template_rendered, url_for, current_app
8
8
  from flask.testing import FlaskClient
9
9
  from lxml import etree
10
10
  from werkzeug.urls import url_encode
11
+ from flask_principal import Identity, identity_changed
11
12
 
12
13
  from udata import settings
13
14
  from udata.app import create_app
@@ -49,6 +50,7 @@ class TestClient(FlaskClient):
49
50
  session['_fresh'] = True
50
51
  session['_id'] = current_app.login_manager._session_identifier_generator()
51
52
  current_app.login_manager._update_request_context_with_user(user)
53
+ identity_changed.send(current_app._get_current_object(), identity=Identity(user.id))
52
54
  return user
53
55
 
54
56
  def logout(self):
@@ -76,7 +76,7 @@ class DiscussionsTest(APITestCase):
76
76
  discussion_id = None
77
77
  def check_signal(args):
78
78
  self.assertIsNotNone(discussion_id)
79
- self.assertIn(f'http://local.test/api/1/datasets/{dataset.id}/#discussion-{discussion_id}', args[1]['message'])
79
+ self.assertIn(f'http://local.test/api/1/datasets/{dataset.slug}/#discussion-{discussion_id}', args[1]['message'])
80
80
 
81
81
  with assert_emit(on_new_potential_spam, assertions_callback=check_signal):
82
82
  response = self.post(url_for('api.discussions'), {
@@ -122,6 +122,29 @@ class DiscussionsTest(APITestCase):
122
122
  self.assertStatus(response, 200)
123
123
  self.assertFalse(discussion.reload().is_spam())
124
124
 
125
+
126
+ @pytest.mark.options(SPAM_WORDS=['spam'])
127
+ def test_spam_by_owner(self):
128
+ user = self.login()
129
+ dataset = Dataset.objects.create(title='Test dataset', owner=user)
130
+
131
+ with assert_not_emit(on_new_potential_spam):
132
+ response = self.post(url_for('api.discussions'), {
133
+ 'title': 'spam and blah',
134
+ 'comment': 'bla bla',
135
+ 'subject': {
136
+ 'class': 'Dataset',
137
+ 'id': dataset.id,
138
+ }
139
+ })
140
+ self.assertStatus(response, 201)
141
+
142
+ with assert_not_emit(on_new_potential_spam):
143
+ response = self.post(url_for('api.discussion', id=response.json['id']), {
144
+ 'comment': 'A comment with spam by owner'
145
+ })
146
+ self.assertStatus(response, 200)
147
+
125
148
  @pytest.mark.options(SPAM_WORDS=['spam'])
126
149
  def test_spam_in_new_discussion_comment(self):
127
150
  self.login()
@@ -495,39 +518,6 @@ class DiscussionsTest(APITestCase):
495
518
  {'comment': "can't comment"})
496
519
  self.assert403(response)
497
520
 
498
- @pytest.mark.options(SPAM_WORDS=['spam'], SPAM_ALLOWED_LANGS=['fr'])
499
- def test_close_discussion_with_spam(self):
500
- owner = self.login()
501
- dataset = Dataset.objects.create(title='Test dataset', owner=owner)
502
- user = UserFactory()
503
- message = Message(content='Premier message', posted_by=user)
504
- discussion = Discussion.objects.create(
505
- subject=dataset,
506
- user=user,
507
- title='test discussion',
508
- discussion=[message]
509
- )
510
- on_new_discussion.send(discussion) # Updating metrics.
511
-
512
- with assert_not_emit(on_discussion_closed):
513
- with assert_emit(on_new_potential_spam):
514
- response = self.post(url_for('api.discussion', id=discussion.id), {
515
- 'comment': 'This is a suspicious, real suspicious message in english.',
516
- 'close': True,
517
- })
518
- self.assert200(response)
519
-
520
- discussion.reload()
521
- self.assertFalse(discussion.is_spam())
522
- self.assertTrue(discussion.discussion[1].is_spam())
523
- self.assertTrue('signal_close' in discussion.discussion[1].spam.callbacks)
524
-
525
- with assert_emit(on_discussion_closed):
526
- admin = self.login(AdminFactory())
527
- response = self.delete(url_for('api.discussion_comment_spam', id=discussion.id, cidx=1))
528
- self.assertStatus(response, 200)
529
- self.assertFalse(discussion.reload().discussion[1].is_spam())
530
-
531
521
  def test_close_discussion_permissions(self):
532
522
  dataset = Dataset.objects.create(title='Test dataset')
533
523
  user = UserFactory()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: udata
3
- Version: 9.0.1.dev29632
3
+ Version: 9.0.1.dev29687
4
4
  Summary: Open data portal
5
5
  Home-page: https://github.com/opendatateam/udata
6
6
  Author: Opendata Team
@@ -147,6 +147,8 @@ It is collectively taken care of by members of the
147
147
  - Improve URL validation errors [#3063](https://github.com/opendatateam/udata/pull/3063) [#2768](https://github.com/opendatateam/udata/pull/2768)
148
148
  - Do not return full dataset objects on dataservices endpoints [#3068](https://github.com/opendatateam/udata/pull/3068)
149
149
  - Update markdown base settings [#3067](https://github.com/opendatateam/udata/pull/3067)
150
+ - Prevent tagging as spam owners' messages [#3071](https://github.com/opendatateam/udata/pull/3071)
151
+ - Add api endpoint /me/org_topics/ [#3070](https://github.com/opendatateam/udata/pull/3070)
150
152
 
151
153
  ## 9.0.0 (2024-06-07)
152
154
 
@@ -166,7 +168,7 @@ It is collectively taken care of by members of the
166
168
  - Allow for series in CSW ISO 19139 DCAT backend [#3028](https://github.com/opendatateam/udata/pull/3028)
167
169
  - 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)
168
170
  - Add `resources_downloads` to datasets metrics [#3042](https://github.com/opendatateam/udata/pull/3042)
169
- - Fix do not override resources extras on admin during update [#3043](https://github.com/opendatateam/udata/pull/3043)
171
+ - Fix do not override resources extras on admin during update [#3043](https://github.com/opendatateam/udata/pull/3043)
170
172
  - Endpoint /users is now protected by admin permissions [#3047](https://github.com/opendatateam/udata/pull/3047)
171
173
  - Fix trailing `/` inside `GeoZone` routes not redirecting. Disallow `/` inside `GeoZone` ids [#3045](https://github.com/opendatateam/udata/pull/3045)
172
174
 
@@ -24,7 +24,7 @@ udata/worker.py,sha256=K-Wafye5-uXP4kQlffRKws2J9YbJ6m6n2QjcVsY8Nsg,118
24
24
  udata/wsgi.py,sha256=P7AJvZ5JqY4uRSBOzaFiBniChWIU9RVQ-Y0PN4vCCMY,77
25
25
  udata/admin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  udata/admin/views.py,sha256=wMlpnC1aINW-6JDk6-kQXhcTYBZH-5wajEuWzVDcIKA,331
27
- udata/api/__init__.py,sha256=I40g3PLG4s-zGNQfjB8_KQGzfs3ZyUrZdajd20vQ9ks,11388
27
+ udata/api/__init__.py,sha256=RhgO7r6ROJzhEybCDmtHRwIiUpE-LjEIUr7dRmNdSeg,11429
28
28
  udata/api/commands.py,sha256=oK2p1VdUvULDdYuvYYpYvY_bdkPJy-KfROfoX71oOuA,3277
29
29
  udata/api/errors.py,sha256=Sy_f3WVrNTUPZjCOogIVgocDdUjnKz149KDi4mMA_Lg,240
30
30
  udata/api/fields.py,sha256=l-Fa27-easR86qgof2bk130jq1N1pNUgGmQzok1UI3Q,3094
@@ -111,7 +111,7 @@ udata/core/discussions/constants.py,sha256=nbZgXESpg0TykIGPxW8xUtHtk7TwQoyOL0Ky4
111
111
  udata/core/discussions/factories.py,sha256=NQd_tD0Izrm67uO5HuuClmluteACrRd9PHrb2IkQ0P0,350
112
112
  udata/core/discussions/forms.py,sha256=daDc8vPDhaXjiEyniugiRC6pyv6OsflgIyO-KoAn6i8,828
113
113
  udata/core/discussions/metrics.py,sha256=qtgyDhM1aPgh8bGU-h-962EKR3J44imC155JVi6jvJI,362
114
- udata/core/discussions/models.py,sha256=9DQ9pYyYdc1VVilaY44TKL_OMJP9_FgYwuo0w9ZZw5s,3360
114
+ udata/core/discussions/models.py,sha256=B9tgaN6rqR4DLfHR6_jiZnaadVNZn0n08zmJRh7TPm8,4041
115
115
  udata/core/discussions/notifications.py,sha256=1lsu8WyyOL4bdt0lx6IW5wTxmQ5gS_7FoncN53g3ToQ,927
116
116
  udata/core/discussions/permissions.py,sha256=q3tXNuNmuXCvZhKyudROcwXF53l-IeDR3pfKSh_hIL0,636
117
117
  udata/core/discussions/signals.py,sha256=zjuF-TiFwu9U_RcgZtHB2Z3W4oBx5hVZy6CCAl5Ekug,543
@@ -185,7 +185,7 @@ udata/core/spam/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
185
185
  udata/core/spam/api.py,sha256=8tVRPorw56kxgN64kme5nLkUfh8Gai9QyqT8aNQn9Xo,1674
186
186
  udata/core/spam/constants.py,sha256=M-wvYlcFnpUDortccIKFHWZ45vbNuMPWSvqKm2itn4w,143
187
187
  udata/core/spam/fields.py,sha256=ppazY9bGnz7mujmDndbxG3pPG_1HDUJCbIufxyD1UNQ,310
188
- udata/core/spam/models.py,sha256=4ylTXJ0EbjvwlPUTCwTMDERe26fCqLjAstuvt2EwGN0,7968
188
+ udata/core/spam/models.py,sha256=sGA4TMXpKbyEbwW8Gk6JpPIiV91JQ4SxWjdVtpPIyls,8093
189
189
  udata/core/spam/signals.py,sha256=4VVLyC2I39LFAot4P56nHzY0xumjMBDz_N0Ff_kgBd0,159
190
190
  udata/core/spam/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
191
191
  udata/core/spam/tests/test_spam.py,sha256=W1Ck_rsnURhFi0fy5xOO0CPpW9MuUFbr-NmPZdk5R4Q,676
@@ -221,13 +221,14 @@ udata/core/topic/api.py,sha256=G3hN4e9rK5mIYvDLvPpAOo_DN2SySAGykVVvXGx4uMY,5105
221
221
  udata/core/topic/apiv2.py,sha256=cf-WUSZ7P6Tss3S8utS-uhreLgGI5XR3nn_1UWiZ_Xs,9846
222
222
  udata/core/topic/factories.py,sha256=ksWcIAoYiKCS48q2-RKMYbNJfz1z9H0fBYM9lswFr-8,717
223
223
  udata/core/topic/forms.py,sha256=XqGI4nANdsm2UkIiGAuVqEdZkN5N9sqJ4VaM_PhTaVQ,987
224
- udata/core/topic/models.py,sha256=Fsq4ONTOhDkgNijhRHXqUAOwtqAcUbjanmjftXN5GNE,2100
225
- udata/core/topic/parsers.py,sha256=p2JCGfjeqb5GQTstZssclzLRLqUHy7KWJ7TDcLSF51M,2103
224
+ udata/core/topic/models.py,sha256=MOBrUNJ32r9Avsz1ueuF5DW3UEHIvxDyMUyxc8II-ig,2157
225
+ udata/core/topic/parsers.py,sha256=4fOvUA5wE9NBtq8liF_mnsgF7obCsfPNzNpgc-tnQf0,2167
226
226
  udata/core/topic/permissions.py,sha256=RtFPPlxuU_Bv7ip6LDO4AoPrKFnIOEs9cCMXaSSmEdk,118
227
227
  udata/core/user/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
228
228
  udata/core/user/activities.py,sha256=AMRbYgum8uJXAS3L-ddQN-xKtKRvsmesDZ0ItBY_jS0,2799
229
229
  udata/core/user/api.py,sha256=ngVaVv17Jwrd4sv1Vp4FduSRan5wv2mOP9y43xWbjPk,12781
230
230
  udata/core/user/api_fields.py,sha256=aWw-vaLy5KqE8vr6HtWzoAbzHI8aoi9BV-m-oY7FJfo,5052
231
+ udata/core/user/apiv2.py,sha256=j9qOqoQw5OQvRy84xcOHtf_SJqbpFwmE0OVdPWGwxjg,1125
231
232
  udata/core/user/commands.py,sha256=DlNBFaojhhPHH4kZazp0NMwYWnzPZXBba_vqH-cfR1U,3156
232
233
  udata/core/user/constants.py,sha256=aTluhTR2RGZ_cdG7-mkEoT5Ndbg8BNUwwzCOld0aLMY,77
233
234
  udata/core/user/factories.py,sha256=JiY-AghapelwhAVAExiUEHS1odyIu4PppWuhtZnkjBY,790
@@ -570,10 +571,10 @@ udata/tests/__init__.py,sha256=BezijRRI6dPPiEWWjLsJFLrhhfsTZgcctcxhVfp4j70,2332
570
571
  udata/tests/es-fake-result.json,sha256=z0CX9Gs-NRj49dmtdFvw8ZKsAbMhDt97Na6JX3ucX34,3155
571
572
  udata/tests/helpers.py,sha256=aaifyevJ1Z8CZ8htRrl8OCG5hGcaHfj0lL8iMEKds9w,6022
572
573
  udata/tests/models.py,sha256=_V0smMb1Z6p3aZv6PorzNN-HiNt_B46Ox1fqXrTJEqk,238
573
- udata/tests/plugin.py,sha256=DXP0H1Sm2fc-okGSKKBOgH8D8x4fl4_1OVhakgQLz4w,11278
574
+ udata/tests/plugin.py,sha256=p8TZcFvlywaLeMXLQOBjZ0wgJM8d11pLYmMtLmXjtxg,11430
574
575
  udata/tests/schemas.json,sha256=szM1jDpkogfOG4xWbjIGjLgG8l9-ZyE3JKQtecJyD1E,4990
575
576
  udata/tests/test_activity.py,sha256=spWfhueuLze0kD-pAnegiL3_Kv5io155jQuFI4sjN7I,3258
576
- udata/tests/test_discussions.py,sha256=zPvKOdcTNGXrvHFp9zqjhKE2fqgUkhb_1F98egXYCL0,31036
577
+ udata/tests/test_discussions.py,sha256=mNRA9PkAkUNLQRmbLjvjF2878yY5jsIuA0_wwiLCGHk,30395
577
578
  udata/tests/test_i18n.py,sha256=BU9E7OoIkJw5tv5JYGLjDGBDsti2HuQ_3OWDKnBxnaM,3191
578
579
  udata/tests/test_linkchecker.py,sha256=KxV1-PuuuqogkHf3jP6JhRsc2QG2dFmFB-vSHOiHkuU,10374
579
580
  udata/tests/test_mail.py,sha256=LK_fOBbFoqbwdaIEXO9XyGPzxY9nrT9FjyD_dlglUdQ,1643
@@ -607,6 +608,7 @@ udata/tests/api/test_transfer_api.py,sha256=aGGJp79YYHQQyMAKhp7urk4eD587v3kbIy-8
607
608
  udata/tests/api/test_user_api.py,sha256=IWMonc6qAsUVAGkxb-FqnwPSO6dGNsdS3c8rLwXvGEA,14659
608
609
  udata/tests/apiv2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
609
610
  udata/tests/apiv2/test_datasets.py,sha256=QDebrTdElry2yf3icjpo7FPm183U2vscHLpZrfap_3Y,18420
611
+ udata/tests/apiv2/test_me_api.py,sha256=GjreAZfH3-j-yDgDqNrtPolUPAiqs_OJVtrQUkMm3Ww,1430
610
612
  udata/tests/apiv2/test_organizations.py,sha256=CpNG8xl13rZXxTN2-JRx9ZkyI7IuQUaOsNuumUxuB3I,6704
611
613
  udata/tests/apiv2/test_swagger.py,sha256=D8jpRqDUmqVkNVYkYaXfvMPUc7OBVs_dMsC13KZciWE,785
612
614
  udata/tests/apiv2/test_topics.py,sha256=UA5LcILq7zUa9TXi1iplrvtcY5C0u-0R8PvTCUWPs2Q,10106
@@ -688,9 +690,9 @@ udata/translations/pt/LC_MESSAGES/udata.mo,sha256=iAUNwbI8ESi8MHkE3ZCYCSIXfFC27z
688
690
  udata/translations/pt/LC_MESSAGES/udata.po,sha256=uTmbHfzyFWrVXUkKSuNFzbGpX7EkUuBdD8fE04d3v5g,44572
689
691
  udata/translations/sr/LC_MESSAGES/udata.mo,sha256=1MbQHvKKNUwzMBWLNsH1qqBehO3aILhQiMhi5u1bY8E,28553
690
692
  udata/translations/sr/LC_MESSAGES/udata.po,sha256=AAryt27Gbkhk7FntCCU8_e7HSXATfsAQhwFOFC8CAj0,51152
691
- udata-9.0.1.dev29632.dist-info/LICENSE,sha256=V8j_M8nAz8PvAOZQocyRDX7keai8UJ9skgmnwqETmdY,34520
692
- udata-9.0.1.dev29632.dist-info/METADATA,sha256=QjoBHHpeuLiJJOaNhcDB35wKeaZcW0tP_7WqCwr-By8,125058
693
- udata-9.0.1.dev29632.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
694
- udata-9.0.1.dev29632.dist-info/entry_points.txt,sha256=3SKiqVy4HUqxf6iWspgMqH8d88Htk6KoLbG1BU-UddQ,451
695
- udata-9.0.1.dev29632.dist-info/top_level.txt,sha256=39OCg-VWFWOq4gCKnjKNu-s3OwFlZIu_dVH8Gl6ndHw,12
696
- udata-9.0.1.dev29632.dist-info/RECORD,,
693
+ udata-9.0.1.dev29687.dist-info/LICENSE,sha256=V8j_M8nAz8PvAOZQocyRDX7keai8UJ9skgmnwqETmdY,34520
694
+ udata-9.0.1.dev29687.dist-info/METADATA,sha256=jjIdR5ae9KfTurgZBxvoS_LZXZxnF6vzfei5iJVwGxE,125249
695
+ udata-9.0.1.dev29687.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
696
+ udata-9.0.1.dev29687.dist-info/entry_points.txt,sha256=3SKiqVy4HUqxf6iWspgMqH8d88Htk6KoLbG1BU-UddQ,451
697
+ udata-9.0.1.dev29687.dist-info/top_level.txt,sha256=39OCg-VWFWOq4gCKnjKNu-s3OwFlZIu_dVH8Gl6ndHw,12
698
+ udata-9.0.1.dev29687.dist-info/RECORD,,