Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom metadata in organization #2921

Merged
merged 30 commits into from Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
63918a2
Add custom metadata in organization
quaxsze Nov 3, 2023
a0bf196
add class for org
quaxsze Nov 7, 2023
ac7b948
Merge branch 'master' into CustomMetadata
quaxsze Nov 7, 2023
dd8b0fe
add dataset validation
quaxsze Nov 13, 2023
0799000
Merge branch 'CustomMetadata' of github.com:opendatateam/udata into C…
quaxsze Nov 13, 2023
a4556d9
add tests
quaxsze Nov 13, 2023
9abcdf8
fix test
quaxsze Nov 13, 2023
a647d05
Merge branch 'master' into CustomMetadata
quaxsze Nov 13, 2023
1aa6f0c
add test
quaxsze Nov 14, 2023
8c9b8e6
Merge branch 'master' into CustomMetadata
quaxsze Nov 14, 2023
b0a6dc3
Merge branch 'master' into CustomMetadata
quaxsze Dec 5, 2023
24b6d37
Merge branch 'master' into CustomMetadata
quaxsze Dec 6, 2023
f142f65
add test
quaxsze Dec 11, 2023
a418a72
Merge branch 'master' into CustomMetadata
quaxsze Dec 11, 2023
7fb2771
Merge branch 'master' into CustomMetadata
quaxsze Dec 12, 2023
ca2ecc9
enhancements
quaxsze Jan 8, 2024
34514f0
Merge branch 'CustomMetadata' of github.com:opendatateam/udata into C…
quaxsze Jan 8, 2024
32c6260
enhancements
quaxsze Jan 8, 2024
7e9aef1
Merge branch 'master' into CustomMetadata
quaxsze Jan 8, 2024
f0cb2d7
enhancements
quaxsze Jan 9, 2024
0eaae58
Merge branch 'master' into CustomMetadata
quaxsze Jan 9, 2024
2f01c3b
Merge branch 'master' into CustomMetadata
maudetes Jan 25, 2024
6fc6cd5
Merge branch 'master' into CustomMetadata
quaxsze Jan 29, 2024
4141b94
changelog
quaxsze Jan 29, 2024
339edbe
Merge branch 'master' into CustomMetadata
quaxsze Feb 5, 2024
d34e9b5
enhancement
quaxsze Feb 5, 2024
3788a7e
Merge branch 'CustomMetadata' of github.com:opendatateam/udata into C…
quaxsze Feb 5, 2024
7c1d3b6
enhancement
quaxsze Feb 15, 2024
b856372
Merge branch 'master' into CustomMetadata
quaxsze Feb 15, 2024
2d6070d
changelog
quaxsze Feb 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
47 changes: 23 additions & 24 deletions udata/core/dataset/models.py
Expand Up @@ -552,30 +552,29 @@ def clean(self):
self.frequency = LEGACY_FREQUENCIES[self.frequency]

for key, value in self.extras.items():
quaxsze marked this conversation as resolved.
Show resolved Hide resolved
if 'custom' in key:
if not self.organization:
raise MongoEngineValidationError(
'Custom metadatas are only accessible to dataset owned by on organization.')
custom_meta = key.split(':')[1]
org_custom = self.organization.extras.get('custom')
if not org_custom:
raise MongoEngineValidationError(
'Dataset\'s organization does not have any custom metadata.')
custom_present = False
custom_type = None
for custom in org_custom:
if custom['title'] == custom_meta:
custom_present = True
custom_type = custom['type']
break
if not custom_present:
raise MongoEngineValidationError(
'Dataset\'s organization did not define the requested custom metadata.')
if custom_type == 'choice':
custom_type = 'list'
if not isinstance(value, locate(custom_type)):
raise MongoEngineValidationError(
'Custom metadata is not of the right type.')
if not key.startswith('custom:'):
continue
if not self.organization:
raise MongoEngineValidationError(
'Custom metadatas are only accessible to dataset owned by on organization.')
custom_meta = key.split(':')[1]
org_custom = self.organization.extras.get('custom', [])
custom_present = False
for custom in org_custom:
if custom['title'] != custom_meta:
continue
custom_present = True
if custom['type'] == 'choice':
if value not in custom['choices']:
raise MongoEngineValidationError(
'Custom metadata choice is not defined by organization.')
quaxsze marked this conversation as resolved.
Show resolved Hide resolved
else:
if not isinstance(value, locate(custom['type'])):
raise MongoEngineValidationError(
'Custom metadata is not of the right type.')
if not custom_present:
raise MongoEngineValidationError(
'Dataset\'s organization did not define the requested custom metadata.')

def url_for(self, *args, **kwargs):
return endpoint_for('datasets.show', 'api.dataset', dataset=self, *args, **kwargs)
Expand Down
10 changes: 5 additions & 5 deletions udata/core/organization/apiv2.py
Expand Up @@ -43,7 +43,7 @@ class OrganizationExtrasAPI(API):
def get(self, org):
'''Get an organization extras given its identifier'''
if org.deleted:
apiv2.abort(410, 'Dataset has been deleted')
apiv2.abort(410, 'Organization has been deleted')
return org.extras

@apiv2.secure
Expand Down Expand Up @@ -79,10 +79,10 @@ def delete(self, org):
if org.deleted:
apiv2.abort(410, 'Organization has been deleted')
EditOrganizationPermission(org).test()
try:
for key in data:
for key in data:
quaxsze marked this conversation as resolved.
Show resolved Hide resolved
try:
del org.extras[key]
except KeyError:
apiv2.abort(404, 'Key not found in existing extras')
except KeyError:
pass
org.save()
return org.extras, 204
28 changes: 22 additions & 6 deletions udata/models/extras_fields.py
Expand Up @@ -71,16 +71,32 @@ def validate(self, values):

errors = {}

expected_keys = ["title", "description", "type", "choices"]
mandatory_keys = ["title", "description", "type"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we could be a bit more concise?

optional_keys = ["choices"]
valid_types = ["str", "int", "float", "bool", "datetime", "date", "choice"]

for elem in values.get('custom', []):
# Check if the dictionary contains the expected keys and only them
if all(key in expected_keys for key in elem.keys()):
mandatory_keys_set = set(mandatory_keys)
optional_keys_set = set(optional_keys)
dict_keys_set = set(elem.keys())
# Check if all mandatory keys are in the dictionary
if not mandatory_keys_set.issubset(dict_keys_set):
errors['custom'] = 'The dictionary does not contain the mandatory keys.'
quaxsze marked this conversation as resolved.
Show resolved Hide resolved
# Combine mandatory and optional keys
all_allowed_keys = mandatory_keys_set.union(optional_keys_set)
# Check if there are any keys in the dictionary that are neither mandatory nor optional
extra_keys = dict_keys_set - all_allowed_keys
# If there are no extra keys, the dictionary is valid
if len(extra_keys) == 0:
if elem.get("type") not in valid_types:
errors['type'] = 'Value should be one of: {types}'.format(types=valid_types)
errors['type'] = ('Type \'{type}\' of \'{title}\' should be one of: {types}'
.format(type=elem.get("type"), title=elem.get("title"), types=valid_types))
else:
errors['custom'] = 'The dictionary does not contain the expected keys or contains extra keys.'
errors['custom'] = 'The dictionary does contains extra keys than allowed ones.'

# If the "choices" key exists, check that it's not an empty list
if "choices" in elem and not elem["choices"]:
errors['custom'] = 'The \'choices\' keys cannot be an empty list.'

if errors:
self.error('Unknown badges types', errors=errors)
self.error('Custom extras error', errors=errors)
2 changes: 1 addition & 1 deletion udata/settings.py
Expand Up @@ -74,7 +74,7 @@ class Defaults(object):

# Flask security settings

SESSION_COOKIE_SECURE = False
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_SAMESITE = None # Can be set to 'Lax' or 'Strict'. See https://flask.palletsprojects.com/en/2.3.x/security/#security-cookie

# Flask-Security-Too settings
Expand Down
9 changes: 5 additions & 4 deletions udata/tests/apiv2/test_datasets.py
Expand Up @@ -298,7 +298,8 @@ def test_dataset_custom_extras_choices(self):
{
"title": "color",
"description": "the colors of the dataset (Hex code)",
"type": "choice"
"type": "choice",
"choices": ["yellow", "blue"]
}
]
}
Expand All @@ -310,15 +311,15 @@ def test_dataset_custom_extras_choices(self):
}
response = self.put(url_for('apiv2.dataset_extras', dataset=dataset), data)
self.assert400(response)
assert 'Custom metadata is not of the right type' in response.json['message']
assert 'Custom metadata choice is not defined by organization' in response.json['message']

data = {
'custom:color': ['eea393', 'ff5733']
'custom:color': 'yellow'
}
response = self.put(url_for('apiv2.dataset_extras', dataset=dataset), data)
self.assert200(response)
dataset.reload()
assert dataset.extras['custom:color'] == ['eea393', 'ff5733']
assert dataset.extras['custom:color'] == 'yellow'


class DatasetResourceExtrasAPITest(APITestCase):
Expand Down