udata 11.0.2.dev18__py3-none-any.whl → 11.0.2.dev20__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_fields.py +18 -20
- udata/core/dataservices/models.py +21 -1
- udata/core/dataservices/tasks.py +3 -0
- udata/core/reuse/models.py +1 -1
- udata/core/topic/factories.py +7 -1
- udata/core/topic/models.py +1 -3
- udata/static/chunks/{11.0f04e49a40a0a381bcce.js → 11.b6f741fcc366abfad9c4.js} +3 -3
- udata/static/chunks/{11.0f04e49a40a0a381bcce.js.map → 11.b6f741fcc366abfad9c4.js.map} +1 -1
- udata/static/chunks/{13.d9c1735d14038b94c17e.js → 13.2d06442dd9a05d9777b5.js} +2 -2
- udata/static/chunks/{13.d9c1735d14038b94c17e.js.map → 13.2d06442dd9a05d9777b5.js.map} +1 -1
- udata/static/chunks/{17.81c57c0dedf812e43013.js → 17.e8e4caaad5cb0cc0bacc.js} +2 -2
- udata/static/chunks/{17.81c57c0dedf812e43013.js.map → 17.e8e4caaad5cb0cc0bacc.js.map} +1 -1
- udata/static/chunks/{19.8da42e8359d72afc2618.js → 19.f03a102365af4315f9db.js} +3 -3
- udata/static/chunks/{19.8da42e8359d72afc2618.js.map → 19.f03a102365af4315f9db.js.map} +1 -1
- udata/static/chunks/{8.494b003a94383b142c18.js → 8.778091d55cd8ea39af6b.js} +2 -2
- udata/static/chunks/{8.494b003a94383b142c18.js.map → 8.778091d55cd8ea39af6b.js.map} +1 -1
- udata/static/common.js +1 -1
- udata/static/common.js.map +1 -1
- udata/tests/api/test_dataservices_api.py +10 -0
- udata/tests/apiv2/test_topics.py +20 -3
- udata/tests/test_api_fields.py +21 -5
- udata/tests/topic/test_topic_tasks.py +8 -1
- {udata-11.0.2.dev18.dist-info → udata-11.0.2.dev20.dist-info}/METADATA +2 -2
- {udata-11.0.2.dev18.dist-info → udata-11.0.2.dev20.dist-info}/RECORD +28 -28
- {udata-11.0.2.dev18.dist-info → udata-11.0.2.dev20.dist-info}/WHEEL +0 -0
- {udata-11.0.2.dev18.dist-info → udata-11.0.2.dev20.dist-info}/entry_points.txt +0 -0
- {udata-11.0.2.dev18.dist-info → udata-11.0.2.dev20.dist-info}/licenses/LICENSE +0 -0
- {udata-11.0.2.dev18.dist-info → udata-11.0.2.dev20.dist-info}/top_level.txt +0 -0
udata/api_fields.py
CHANGED
|
@@ -8,10 +8,11 @@ Main components:
|
|
|
8
8
|
- `field()`: Universal function to add metadata to fields and methods
|
|
9
9
|
|
|
10
10
|
The `@generate_fields` decorator parameters:
|
|
11
|
-
- default_filterable_field:
|
|
12
|
-
- searchable:
|
|
13
|
-
- additional_sorts:
|
|
14
|
-
-
|
|
11
|
+
- default_filterable_field: which field in this document should be the default filter, eg when filtering by Badge, you're actually filtering on `Badge.kind`
|
|
12
|
+
- searchable: boolean, if True, the document can be full-text searched using MongoEngine text search
|
|
13
|
+
- additional_sorts: add more sorts than the already available ones based on fields (see below). Eg, sort by metrics.
|
|
14
|
+
- nested_filters: filter on a field of a field (aka "join"), eg filter on `Reuse__organization__badge=PUBLIC_SERVICE`.
|
|
15
|
+
- standalone_filters: filter on something else than a field. Should be a list of dicts with filterable attributes, as returned by `compute_filter`.
|
|
15
16
|
|
|
16
17
|
Generated attributes on decorated classes:
|
|
17
18
|
- ref_fields: Minimal fields for embedded/referenced documents
|
|
@@ -317,9 +318,9 @@ def generate_fields(**kwargs) -> Callable:
|
|
|
317
318
|
ref_fields: dict = {}
|
|
318
319
|
sortables: list = kwargs.get("additional_sorts", [])
|
|
319
320
|
|
|
320
|
-
filterables: list[dict] = []
|
|
321
|
-
|
|
322
|
-
kwargs.get("
|
|
321
|
+
filterables: list[dict] = kwargs.get("standalone_filters", [])
|
|
322
|
+
nested_filters: dict[str, dict] = get_fields_with_nested_filters(
|
|
323
|
+
kwargs.get("nested_filters", {})
|
|
323
324
|
)
|
|
324
325
|
|
|
325
326
|
read_fields["id"] = restx_fields.String(required=True, readonly=True)
|
|
@@ -341,16 +342,16 @@ def generate_fields(**kwargs) -> Callable:
|
|
|
341
342
|
if filterable is not None:
|
|
342
343
|
filterables.append(compute_filter(key, field, info, filterable))
|
|
343
344
|
|
|
344
|
-
|
|
345
|
-
if
|
|
345
|
+
nested_filter: dict | None = nested_filters.get(key, None)
|
|
346
|
+
if nested_filter:
|
|
346
347
|
if not isinstance(
|
|
347
348
|
field, mongo_fields.ReferenceField | mongo_fields.LazyReferenceField
|
|
348
349
|
):
|
|
349
|
-
raise Exception("Cannot use
|
|
350
|
+
raise Exception("Cannot use nested_filters on a field that is not a ref.")
|
|
350
351
|
|
|
351
352
|
ref_model: db.Document = field.document_type
|
|
352
353
|
|
|
353
|
-
for child in
|
|
354
|
+
for child in nested_filter.get("children", []):
|
|
354
355
|
inner_field: str = getattr(ref_model, child["key"])
|
|
355
356
|
|
|
356
357
|
column: str = f"{key}__{child['key']}"
|
|
@@ -474,7 +475,7 @@ def generate_fields(**kwargs) -> Callable:
|
|
|
474
475
|
|
|
475
476
|
for filterable in filterables:
|
|
476
477
|
parser.add_argument(
|
|
477
|
-
# Use the custom label from `
|
|
478
|
+
# Use the custom label from `nested_filters` if there's one.
|
|
478
479
|
filterable.get("label", filterable["key"]),
|
|
479
480
|
type=filterable["type"],
|
|
480
481
|
location="args",
|
|
@@ -505,7 +506,7 @@ def generate_fields(**kwargs) -> Callable:
|
|
|
505
506
|
base_query = base_query.search_text(phrase_query)
|
|
506
507
|
|
|
507
508
|
for filterable in filterables:
|
|
508
|
-
# If it's from an `
|
|
509
|
+
# If it's from an `nested_filter`, use the custom label instead of the key,
|
|
509
510
|
# eg use `organization_badge` instead of `organization.badges` which is
|
|
510
511
|
# computed to `organization_badges`.
|
|
511
512
|
filter = args.get(filterable.get("label", filterable["key"]))
|
|
@@ -792,21 +793,18 @@ def wrap_primary_key(
|
|
|
792
793
|
)
|
|
793
794
|
|
|
794
795
|
|
|
795
|
-
def
|
|
796
|
+
def get_fields_with_nested_filters(nested_filters: dict[str, str]) -> dict[str, Any]:
|
|
796
797
|
"""Filter on additional related fields.
|
|
797
798
|
|
|
798
|
-
Right now we only support additional filters with a depth of two, eg "organization.badges".
|
|
799
|
-
|
|
800
|
-
The goal of this function is to key by the additional filters by the first part (`organization`) to
|
|
801
799
|
be able to compute them when we loop over all the fields (`title`, `organization`…)
|
|
802
800
|
|
|
803
801
|
|
|
804
|
-
The `
|
|
802
|
+
The `nested_filters` property is a dict: {"label": "key"}, for example {"organization_badge": "organization.badges"}.
|
|
805
803
|
The `label` will be the name of the parser arg, like `?organization_badge=public-service`, which makes more
|
|
806
804
|
sense than `?organization_badges=public-service`.
|
|
807
805
|
"""
|
|
808
806
|
results: dict = {}
|
|
809
|
-
for label, key in
|
|
807
|
+
for label, key in nested_filters.items():
|
|
810
808
|
parts = key.split(".")
|
|
811
809
|
if len(parts) == 2:
|
|
812
810
|
parent = parts[0]
|
|
@@ -824,7 +822,7 @@ def get_fields_with_additional_filters(additional_filters: dict[str, str]) -> di
|
|
|
824
822
|
}
|
|
825
823
|
)
|
|
826
824
|
else:
|
|
827
|
-
raise Exception(f"Do not support `
|
|
825
|
+
raise Exception(f"Do not support `nested_filters` without two parts: {key}.")
|
|
828
826
|
|
|
829
827
|
return results
|
|
830
828
|
|
|
@@ -134,9 +134,29 @@ def check_only_one_condition_per_role(access_audiences, **_kwargs):
|
|
|
134
134
|
)
|
|
135
135
|
|
|
136
136
|
|
|
137
|
+
def filter_by_topic(base_query, filter_value):
|
|
138
|
+
from udata.core.topic.models import Topic
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
topic = Topic.objects.get(id=filter_value)
|
|
142
|
+
except Topic.DoesNotExist:
|
|
143
|
+
pass
|
|
144
|
+
else:
|
|
145
|
+
return base_query.filter(
|
|
146
|
+
id__in=[
|
|
147
|
+
elt.element.id
|
|
148
|
+
for elt in topic.elements
|
|
149
|
+
if elt.element.__class__.__name__ == "Dataservice"
|
|
150
|
+
]
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
137
154
|
@generate_fields(
|
|
138
155
|
searchable=True,
|
|
139
|
-
|
|
156
|
+
nested_filters={"organization_badge": "organization.badges"},
|
|
157
|
+
standalone_filters=[
|
|
158
|
+
{"key": "topic", "constraints": "objectid", "query": filter_by_topic, "type": str}
|
|
159
|
+
],
|
|
140
160
|
additional_sorts=[
|
|
141
161
|
{"key": "followers", "value": "metrics.followers"},
|
|
142
162
|
{"key": "views", "value": "metrics.views"},
|
udata/core/dataservices/tasks.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from celery.utils.log import get_task_logger
|
|
2
2
|
|
|
3
3
|
from udata.core.dataservices.models import Dataservice
|
|
4
|
+
from udata.core.topic.models import TopicElement
|
|
4
5
|
from udata.harvest.models import HarvestJob
|
|
5
6
|
from udata.models import Discussion, Follow, Transfer
|
|
6
7
|
from udata.tasks import job
|
|
@@ -20,5 +21,7 @@ def purge_dataservices(self):
|
|
|
20
21
|
HarvestJob.objects(items__dataservice=dataservice).update(set__items__S__dataservice=None)
|
|
21
22
|
# Remove associated Transfers
|
|
22
23
|
Transfer.objects(subject=dataservice).delete()
|
|
24
|
+
# Remove dataservices references in Topics
|
|
25
|
+
TopicElement.objects(element=dataservice).update(element=None)
|
|
23
26
|
# Remove dataservice
|
|
24
27
|
dataservice.delete()
|
udata/core/reuse/models.py
CHANGED
|
@@ -60,7 +60,7 @@ class ReuseBadgeMixin(BadgeMixin):
|
|
|
60
60
|
{"key": "followers", "value": "metrics.followers"},
|
|
61
61
|
{"key": "views", "value": "metrics.views"},
|
|
62
62
|
],
|
|
63
|
-
|
|
63
|
+
nested_filters={"organization_badge": "organization.badges"},
|
|
64
64
|
mask="*,datasets{id,title,uri,page}",
|
|
65
65
|
)
|
|
66
66
|
class Reuse(db.Datetimed, Auditable, WithMetrics, ReuseBadgeMixin, Linkable, Owned, db.Document):
|
udata/core/topic/factories.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import factory
|
|
2
2
|
|
|
3
3
|
from udata import utils
|
|
4
|
+
from udata.core.dataservices.factories import DataserviceFactory
|
|
4
5
|
from udata.core.dataset.factories import DatasetFactory
|
|
5
6
|
from udata.core.reuse.factories import ReuseFactory
|
|
6
7
|
from udata.factories import ModelFactory
|
|
@@ -38,6 +39,10 @@ class TopicElementReuseFactory(TopicElementFactory):
|
|
|
38
39
|
element = factory.SubFactory(ReuseFactory)
|
|
39
40
|
|
|
40
41
|
|
|
42
|
+
class TopicElementDataserviceFactory(TopicElementFactory):
|
|
43
|
+
element = factory.SubFactory(DataserviceFactory)
|
|
44
|
+
|
|
45
|
+
|
|
41
46
|
class TopicFactory(ModelFactory):
|
|
42
47
|
class Meta:
|
|
43
48
|
model = Topic
|
|
@@ -58,9 +63,10 @@ class TopicWithElementsFactory(TopicFactory):
|
|
|
58
63
|
# Create associated elements
|
|
59
64
|
TopicElementDatasetFactory.create_batch(2, topic=self)
|
|
60
65
|
TopicElementReuseFactory.create(topic=self)
|
|
66
|
+
TopicElementDataserviceFactory.create(topic=self)
|
|
61
67
|
|
|
62
68
|
@classmethod
|
|
63
|
-
def elements_as_payload(cls, elements: list) -> dict:
|
|
69
|
+
def elements_as_payload(cls, elements: list) -> list[dict]:
|
|
64
70
|
return [
|
|
65
71
|
{
|
|
66
72
|
"element": {"id": str(elt.element.id), "class": elt.element.__class__.__name__},
|
udata/core/topic/models.py
CHANGED
|
@@ -4,10 +4,8 @@ from mongoengine.signals import post_delete, post_save
|
|
|
4
4
|
|
|
5
5
|
from udata.api_fields import field
|
|
6
6
|
from udata.core.activity.models import Auditable
|
|
7
|
-
from udata.core.dataset.models import Dataset
|
|
8
7
|
from udata.core.linkable import Linkable
|
|
9
8
|
from udata.core.owned import Owned, OwnedQuerySet
|
|
10
|
-
from udata.core.reuse.models import Reuse
|
|
11
9
|
from udata.models import SpatialCoverage, db
|
|
12
10
|
from udata.search import reindex
|
|
13
11
|
from udata.tasks import as_task_param
|
|
@@ -20,7 +18,7 @@ class TopicElement(Auditable, db.Document):
|
|
|
20
18
|
description = field(db.StringField(required=False))
|
|
21
19
|
tags = field(db.ListField(db.StringField()))
|
|
22
20
|
extras = field(db.ExtrasField())
|
|
23
|
-
element = field(db.GenericReferenceField(choices=[Dataset, Reuse]))
|
|
21
|
+
element = field(db.GenericReferenceField(choices=["Dataset", "Reuse", "Dataservice"]))
|
|
24
22
|
# Made optional to allow proper form handling with commit=False
|
|
25
23
|
topic = field(db.ReferenceField("Topic", required=False))
|
|
26
24
|
|