udata 13.0.1.dev12__py3-none-any.whl → 14.4.1.dev7__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 +2 -8
- udata/api_fields.py +35 -4
- udata/app.py +30 -50
- udata/auth/__init__.py +29 -6
- udata/auth/forms.py +8 -6
- udata/auth/views.py +6 -3
- udata/commands/__init__.py +2 -14
- udata/commands/db.py +13 -25
- udata/commands/info.py +0 -16
- udata/commands/serve.py +3 -11
- udata/commands/tests/test_fixtures.py +9 -9
- udata/core/access_type/api.py +1 -1
- udata/core/access_type/constants.py +12 -8
- udata/core/activity/api.py +5 -6
- udata/core/avatars/api.py +43 -0
- udata/core/avatars/test_avatar_api.py +30 -0
- udata/core/badges/tests/test_commands.py +6 -6
- udata/core/csv.py +5 -0
- udata/core/dataservices/models.py +15 -3
- udata/core/dataservices/tasks.py +7 -0
- udata/core/dataset/api.py +2 -0
- udata/core/dataset/models.py +2 -2
- udata/core/dataset/permissions.py +31 -0
- udata/core/dataset/tasks.py +50 -10
- udata/core/discussions/models.py +1 -0
- udata/core/metrics/__init__.py +0 -6
- udata/core/organization/api.py +8 -5
- udata/core/organization/mails.py +1 -1
- udata/core/organization/models.py +9 -1
- udata/core/organization/notifications.py +84 -0
- udata/core/organization/permissions.py +1 -1
- udata/core/organization/tasks.py +3 -0
- udata/core/pages/tests/test_api.py +32 -0
- udata/core/post/api.py +24 -69
- udata/core/post/models.py +84 -16
- udata/core/post/tests/test_api.py +24 -1
- udata/core/reports/api.py +18 -0
- udata/core/reports/models.py +42 -2
- udata/core/reuse/models.py +1 -1
- udata/core/reuse/tasks.py +7 -0
- udata/core/site/models.py +2 -6
- udata/core/spatial/commands.py +2 -4
- udata/core/spatial/forms.py +2 -2
- udata/core/spatial/models.py +0 -10
- udata/core/spatial/tests/test_api.py +1 -36
- udata/core/user/models.py +15 -2
- udata/cors.py +2 -5
- udata/db/migrations.py +279 -0
- udata/features/notifications/api.py +7 -18
- udata/features/notifications/models.py +56 -0
- udata/features/notifications/tasks.py +25 -0
- udata/flask_mongoengine/engine.py +0 -4
- udata/frontend/__init__.py +3 -122
- udata/frontend/markdown.py +2 -1
- udata/harvest/actions.py +24 -9
- udata/harvest/api.py +30 -22
- udata/harvest/backends/__init__.py +21 -9
- udata/harvest/backends/base.py +29 -3
- udata/harvest/backends/ckan/harvesters.py +13 -2
- udata/harvest/backends/dcat.py +3 -0
- udata/harvest/backends/maaf.py +1 -0
- udata/harvest/commands.py +39 -4
- udata/harvest/filters.py +17 -6
- udata/harvest/forms.py +9 -6
- udata/harvest/models.py +16 -0
- udata/harvest/permissions.py +27 -0
- udata/harvest/tasks.py +3 -5
- udata/harvest/tests/ckan/test_ckan_backend.py +35 -2
- udata/harvest/tests/ckan/test_ckan_backend_errors.py +1 -1
- udata/harvest/tests/ckan/test_ckan_backend_filters.py +1 -1
- udata/harvest/tests/ckan/test_dkan_backend.py +1 -1
- udata/harvest/tests/dcat/udata.xml +6 -6
- udata/harvest/tests/factories.py +1 -1
- udata/harvest/tests/test_actions.py +63 -8
- udata/harvest/tests/test_api.py +278 -123
- udata/harvest/tests/test_base_backend.py +88 -1
- udata/harvest/tests/test_dcat_backend.py +60 -13
- udata/harvest/tests/test_filters.py +6 -0
- udata/i18n.py +11 -273
- udata/mail.py +5 -1
- udata/migrations/2025-10-31-create-membership-request-notifications.py +55 -0
- udata/migrations/2025-11-13-delete-user-email-index.py +25 -0
- udata/migrations/2025-12-04-add-uuid-to-discussion-messages.py +28 -0
- udata/models/__init__.py +0 -8
- udata/mongo/slug_fields.py +1 -1
- udata/rdf.py +45 -6
- udata/routing.py +2 -10
- udata/sentry.py +4 -10
- udata/settings.py +23 -17
- udata/tasks.py +4 -3
- udata/templates/mail/message.html +5 -31
- udata/tests/__init__.py +28 -12
- udata/tests/api/__init__.py +108 -21
- udata/tests/api/test_activities_api.py +36 -0
- udata/tests/api/test_auth_api.py +121 -95
- udata/tests/api/test_base_api.py +7 -4
- udata/tests/api/test_dataservices_api.py +29 -1
- udata/tests/api/test_datasets_api.py +45 -21
- udata/tests/api/test_organizations_api.py +192 -197
- udata/tests/api/test_reports_api.py +157 -0
- udata/tests/api/test_reuses_api.py +147 -147
- udata/tests/api/test_security_api.py +12 -12
- udata/tests/api/test_swagger.py +4 -4
- udata/tests/api/test_tags_api.py +8 -8
- udata/tests/api/test_user_api.py +13 -1
- udata/tests/apiv2/test_swagger.py +4 -4
- udata/tests/apiv2/test_topics.py +1 -1
- udata/tests/cli/test_cli_base.py +8 -9
- udata/tests/dataset/test_dataset_commands.py +4 -4
- udata/tests/dataset/test_dataset_model.py +66 -26
- udata/tests/dataset/test_dataset_rdf.py +99 -5
- udata/tests/dataset/test_resource_preview.py +0 -1
- udata/tests/frontend/test_auth.py +24 -1
- udata/tests/frontend/test_csv.py +0 -3
- udata/tests/helpers.py +37 -27
- udata/tests/organization/test_notifications.py +67 -2
- udata/tests/plugin.py +6 -261
- udata/tests/site/test_site_csv_exports.py +22 -10
- udata/tests/test_activity.py +9 -9
- udata/tests/test_cors.py +1 -1
- udata/tests/test_dcat_commands.py +2 -2
- udata/tests/test_discussions.py +5 -5
- udata/tests/test_migrations.py +181 -481
- udata/tests/test_notifications.py +15 -57
- udata/tests/test_notifications_task.py +43 -0
- udata/tests/test_owned.py +81 -1
- udata/tests/test_storages.py +25 -19
- udata/tests/test_topics.py +77 -61
- udata/tests/test_uris.py +33 -0
- udata/tests/workers/test_jobs_commands.py +23 -23
- udata/translations/ar/LC_MESSAGES/udata.mo +0 -0
- udata/translations/ar/LC_MESSAGES/udata.po +187 -108
- udata/translations/de/LC_MESSAGES/udata.mo +0 -0
- udata/translations/de/LC_MESSAGES/udata.po +187 -108
- udata/translations/es/LC_MESSAGES/udata.mo +0 -0
- udata/translations/es/LC_MESSAGES/udata.po +187 -108
- udata/translations/fr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/fr/LC_MESSAGES/udata.po +188 -109
- udata/translations/it/LC_MESSAGES/udata.mo +0 -0
- udata/translations/it/LC_MESSAGES/udata.po +187 -108
- udata/translations/pt/LC_MESSAGES/udata.mo +0 -0
- udata/translations/pt/LC_MESSAGES/udata.po +187 -108
- udata/translations/sr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/sr/LC_MESSAGES/udata.po +187 -108
- udata/translations/udata.pot +215 -106
- udata/uris.py +0 -2
- udata/utils.py +5 -0
- udata-14.4.1.dev7.dist-info/METADATA +109 -0
- {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/RECORD +153 -166
- {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/entry_points.txt +3 -5
- udata/core/followers/views.py +0 -15
- udata/core/post/forms.py +0 -30
- udata/entrypoints.py +0 -93
- udata/features/identicon/__init__.py +0 -0
- udata/features/identicon/api.py +0 -13
- udata/features/identicon/backends.py +0 -131
- udata/features/identicon/tests/__init__.py +0 -0
- udata/features/identicon/tests/test_backends.py +0 -18
- udata/features/territories/__init__.py +0 -49
- udata/features/territories/api.py +0 -25
- udata/features/territories/models.py +0 -51
- udata/flask_mongoengine/json.py +0 -38
- udata/migrations/__init__.py +0 -367
- udata/templates/mail/base.html +0 -105
- udata/templates/mail/base.txt +0 -6
- udata/templates/mail/button.html +0 -3
- udata/templates/mail/layouts/1-column.html +0 -19
- udata/templates/mail/layouts/2-columns.html +0 -20
- udata/templates/mail/layouts/center-panel.html +0 -16
- udata/tests/cli/test_db_cli.py +0 -68
- udata/tests/features/territories/__init__.py +0 -20
- udata/tests/features/territories/test_territories_api.py +0 -185
- udata/tests/frontend/test_hooks.py +0 -149
- udata-13.0.1.dev12.dist-info/METADATA +0 -133
- {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/WHEEL +0 -0
- {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/licenses/LICENSE +0 -0
- {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/top_level.txt +0 -0
|
@@ -4,11 +4,6 @@ udata = udata.commands:cli
|
|
|
4
4
|
[pytest11]
|
|
5
5
|
udata = udata.tests.plugin
|
|
6
6
|
|
|
7
|
-
[udata.avatars]
|
|
8
|
-
adorable = udata.features.identicon.backends:adorable
|
|
9
|
-
internal = udata.features.identicon.backends:internal
|
|
10
|
-
robohash = udata.features.identicon.backends:robohash
|
|
11
|
-
|
|
12
7
|
[udata.harvesters]
|
|
13
8
|
ckan = udata.harvest.backends.ckan.harvesters:CkanBackend
|
|
14
9
|
csw-dcat = udata.harvest.backends.dcat:CswDcatBackend
|
|
@@ -16,3 +11,6 @@ csw-iso-19139 = udata.harvest.backends.dcat:CswIso19139DcatBackend
|
|
|
16
11
|
dcat = udata.harvest.backends.dcat:DcatBackend
|
|
17
12
|
dkan = udata.harvest.backends.ckan.harvesters:DkanBackend
|
|
18
13
|
maaf = udata.harvest.backends.maaf:MaafBackend
|
|
14
|
+
|
|
15
|
+
[udata.i18n]
|
|
16
|
+
udata = udata.translations
|
udata/core/followers/views.py
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# from udata.auth import current_user
|
|
2
|
-
|
|
3
|
-
# from udata.i18n import I18nBlueprint
|
|
4
|
-
|
|
5
|
-
# from .models import Follow
|
|
6
|
-
|
|
7
|
-
# blueprint = I18nBlueprint('followers', __name__)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
# @blueprint.app_template_global()
|
|
11
|
-
# @blueprint.app_template_filter()
|
|
12
|
-
# def is_following(obj):
|
|
13
|
-
# if not current_user.is_authenticated:
|
|
14
|
-
# return False
|
|
15
|
-
# return Follow.objects.is_following(current_user._get_current_object(), obj)
|
udata/core/post/forms.py
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
from udata.forms import ModelForm, fields, validators, widgets
|
|
2
|
-
from udata.i18n import lazy_gettext as _
|
|
3
|
-
|
|
4
|
-
from .constants import IMAGE_SIZES
|
|
5
|
-
from .models import Post
|
|
6
|
-
|
|
7
|
-
__all__ = ("PostForm",)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class PostForm(ModelForm):
|
|
11
|
-
model_class = Post
|
|
12
|
-
|
|
13
|
-
owner = fields.CurrentUserField()
|
|
14
|
-
|
|
15
|
-
name = fields.StringField(_("Name"), [validators.DataRequired()])
|
|
16
|
-
headline = fields.StringField(_("Headline"), widget=widgets.TextArea())
|
|
17
|
-
content = fields.MarkdownField(_("Content"), [validators.DataRequired()])
|
|
18
|
-
|
|
19
|
-
datasets = fields.DatasetListField(_("Associated datasets"))
|
|
20
|
-
reuses = fields.ReuseListField(_("Associated reuses"))
|
|
21
|
-
|
|
22
|
-
image = fields.ImageField(_("Image"), sizes=IMAGE_SIZES)
|
|
23
|
-
credit_to = fields.StringField(_("Image credits"))
|
|
24
|
-
credit_url = fields.URLField(_("Credit URL"))
|
|
25
|
-
|
|
26
|
-
tags = fields.TagField(_("Tags"))
|
|
27
|
-
|
|
28
|
-
body_type = fields.StringField(
|
|
29
|
-
_("body type"), description=_("Specify your body type (HTML or markdown)")
|
|
30
|
-
)
|
udata/entrypoints.py
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import pkg_resources
|
|
2
|
-
|
|
3
|
-
# Here for documentation purpose
|
|
4
|
-
ENTRYPOINTS = {
|
|
5
|
-
"udata.avatars": "Avatar rendering backends",
|
|
6
|
-
"udata.harvesters": "Harvest backends",
|
|
7
|
-
"udata.metrics": "Extra metrics",
|
|
8
|
-
"udata.models": "Models and migrations",
|
|
9
|
-
"udata.plugins": "Generic plugin",
|
|
10
|
-
"udata.tasks": "Tasks and jobs",
|
|
11
|
-
"udata.views": "Extra views",
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class EntrypointError(Exception):
|
|
16
|
-
pass
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def iter_all(name):
|
|
20
|
-
"""Iter all entrypoints registered on a given key"""
|
|
21
|
-
return pkg_resources.iter_entry_points(name)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def get_all(entrypoint_key):
|
|
25
|
-
"""Load all entrypoints registered on a given key"""
|
|
26
|
-
return dict(_ep_to_kv(e) for e in iter_all(entrypoint_key))
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def get_enabled(name, app):
|
|
30
|
-
"""
|
|
31
|
-
Get (and load) entrypoints registered on name
|
|
32
|
-
and enabled for the given app.
|
|
33
|
-
"""
|
|
34
|
-
plugins = app.config["PLUGINS"]
|
|
35
|
-
return dict(
|
|
36
|
-
_ep_to_kv(e)
|
|
37
|
-
for e in iter_all(name)
|
|
38
|
-
if e.name in plugins or e.name.startswith(tuple(plugins))
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def get_plugin_module(name, app, plugin):
|
|
43
|
-
"""
|
|
44
|
-
Get the module for a given plugin
|
|
45
|
-
"""
|
|
46
|
-
return next((m for p, m in get_enabled(name, app).items() if p == plugin), None)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def _ep_to_kv(entrypoint):
|
|
50
|
-
"""
|
|
51
|
-
Transform an entrypoint into a key-value tuple where:
|
|
52
|
-
- key is the entrypoint name
|
|
53
|
-
- value is the entrypoint class with the name attribute
|
|
54
|
-
matching from entrypoint name
|
|
55
|
-
"""
|
|
56
|
-
cls = entrypoint.load()
|
|
57
|
-
cls.name = entrypoint.name
|
|
58
|
-
return (entrypoint.name, cls)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def known_dists():
|
|
62
|
-
"""Return a list of all Distributions exporting udata.* entrypoints"""
|
|
63
|
-
return (
|
|
64
|
-
dist
|
|
65
|
-
for dist in pkg_resources.working_set
|
|
66
|
-
if any(k in ENTRYPOINTS for k in dist.get_entry_map().keys())
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def get_plugins_dists(app, name=None):
|
|
71
|
-
"""Return a list of Distributions with enabled udata plugins"""
|
|
72
|
-
if name:
|
|
73
|
-
plugins = set(e.name for e in iter_all(name) if e.name in app.config["PLUGINS"])
|
|
74
|
-
else:
|
|
75
|
-
plugins = set(app.config["PLUGINS"])
|
|
76
|
-
return [
|
|
77
|
-
d for d in known_dists() if any(set(v.keys()) & plugins for v in d.get_entry_map().values())
|
|
78
|
-
]
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def get_roots(app=None):
|
|
82
|
-
"""
|
|
83
|
-
Returns the list of root packages/modules exposing endpoints.
|
|
84
|
-
|
|
85
|
-
If app is provided, only returns those of enabled plugins
|
|
86
|
-
"""
|
|
87
|
-
roots = set()
|
|
88
|
-
plugins = app.config["PLUGINS"] if app else None
|
|
89
|
-
for name in ENTRYPOINTS.keys():
|
|
90
|
-
for ep in iter_all(name):
|
|
91
|
-
if plugins is None or ep.name in plugins:
|
|
92
|
-
roots.add(ep.module_name.split(".", 1)[0])
|
|
93
|
-
return list(roots)
|
|
File without changes
|
udata/features/identicon/api.py
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from udata.api import API, api
|
|
2
|
-
|
|
3
|
-
from . import backends
|
|
4
|
-
|
|
5
|
-
ns = api.namespace("avatars", "Avatars")
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@ns.route("/<identifier>/<int:size>/", endpoint="avatar")
|
|
9
|
-
class IdenticonAPI(API):
|
|
10
|
-
@api.doc("avatars")
|
|
11
|
-
def get(self, identifier, size):
|
|
12
|
-
"""Get a deterministic avatar given an identifier at a given size"""
|
|
13
|
-
return backends.get_identicon(identifier, size)
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import hashlib
|
|
2
|
-
import io
|
|
3
|
-
|
|
4
|
-
import pydenticon
|
|
5
|
-
from flask import current_app, redirect, send_file
|
|
6
|
-
|
|
7
|
-
from udata import entrypoints
|
|
8
|
-
from udata.app import cache
|
|
9
|
-
|
|
10
|
-
ADORABLE_AVATARS_URL = "https://api.adorable.io/avatars/{size}/{identifier}.png" # noqa
|
|
11
|
-
ROBOHASH_URL = "https://robohash.org/{identifier}.png?size={size}x{size}&set={skin}&bgset={bg}" # noqa
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
# Default values overriden by theme default and local config
|
|
15
|
-
DEFAULTS = {
|
|
16
|
-
"AVATAR_PROVIDER": "internal",
|
|
17
|
-
# Internal provider
|
|
18
|
-
"AVATAR_INTERNAL_SIZE": 7,
|
|
19
|
-
"AVATAR_INTERNAL_FOREGROUND": [
|
|
20
|
-
"rgb(45,79,255)",
|
|
21
|
-
"rgb(254,180,44)",
|
|
22
|
-
"rgb(226,121,234)",
|
|
23
|
-
"rgb(30,179,253)",
|
|
24
|
-
"rgb(232,77,65)",
|
|
25
|
-
"rgb(49,203,115)",
|
|
26
|
-
"rgb(141,69,170)",
|
|
27
|
-
],
|
|
28
|
-
"AVATAR_INTERNAL_BACKGROUND": "rgb(224,224,224)",
|
|
29
|
-
"AVATAR_INTERNAL_PADDING": 10,
|
|
30
|
-
# robohash prodiver
|
|
31
|
-
"AVATAR_ROBOHASH_SKIN": "set1",
|
|
32
|
-
"AVATAR_ROBOHASH_BACKGROUND": "bg1",
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def get_config(key):
|
|
37
|
-
"""
|
|
38
|
-
Get an identicon configuration parameter.
|
|
39
|
-
|
|
40
|
-
Precedance order is:
|
|
41
|
-
- application config (`udata.cfg`)
|
|
42
|
-
- default
|
|
43
|
-
"""
|
|
44
|
-
key = "AVATAR_{0}".format(key.upper())
|
|
45
|
-
local_config = current_app.config.get(key)
|
|
46
|
-
return local_config or DEFAULTS[key]
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def get_internal_config(key):
|
|
50
|
-
return get_config("internal_{0}".format(key))
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def get_provider():
|
|
54
|
-
"""Get the current provider from config"""
|
|
55
|
-
name = get_config("provider")
|
|
56
|
-
available = entrypoints.get_all("udata.avatars")
|
|
57
|
-
if name not in available:
|
|
58
|
-
raise ValueError("Unknown avatar provider: {0}".format(name))
|
|
59
|
-
return available[name]
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def get_identicon(identifier, size):
|
|
63
|
-
"""
|
|
64
|
-
Get an identicon for a given identifier at a given size.
|
|
65
|
-
|
|
66
|
-
Automatically select the provider from `AVATAR_PROVIDER`
|
|
67
|
-
|
|
68
|
-
:returns: a HTTP response, either an image or a redirect
|
|
69
|
-
"""
|
|
70
|
-
return get_provider()(identifier, size)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
@cache.memoize()
|
|
74
|
-
def generate_pydenticon(identifier, size):
|
|
75
|
-
"""
|
|
76
|
-
Use pydenticon to generate an identicon image.
|
|
77
|
-
All parameters are extracted from configuration.
|
|
78
|
-
"""
|
|
79
|
-
blocks_size = get_internal_config("size")
|
|
80
|
-
foreground = get_internal_config("foreground")
|
|
81
|
-
background = get_internal_config("background")
|
|
82
|
-
generator = pydenticon.Generator(
|
|
83
|
-
blocks_size, blocks_size, digest=hashlib.sha1, foreground=foreground, background=background
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
# Pydenticon adds padding to the size and as a consequence
|
|
87
|
-
# we need to compute the size without the padding
|
|
88
|
-
padding = int(round(get_internal_config("padding") * size / 100.0))
|
|
89
|
-
size = size - 2 * padding
|
|
90
|
-
padding = (padding,) * 4
|
|
91
|
-
return generator.generate(identifier, size, size, padding=padding, output_format="png")
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def internal(identifier, size):
|
|
95
|
-
"""
|
|
96
|
-
Internal provider
|
|
97
|
-
|
|
98
|
-
Use pydenticon to generate an identicon.
|
|
99
|
-
"""
|
|
100
|
-
identicon = generate_pydenticon(identifier, size)
|
|
101
|
-
response = send_file(io.BytesIO(identicon), mimetype="image/png")
|
|
102
|
-
etag = hashlib.sha1(identicon).hexdigest()
|
|
103
|
-
response.set_etag(etag)
|
|
104
|
-
return response
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def adorable(identifier, size):
|
|
108
|
-
"""
|
|
109
|
-
Adorable Avatars provider
|
|
110
|
-
|
|
111
|
-
Simply redirect to the external API.
|
|
112
|
-
|
|
113
|
-
See: http://avatars.adorable.io/
|
|
114
|
-
"""
|
|
115
|
-
url = ADORABLE_AVATARS_URL.format(identifier=identifier, size=size)
|
|
116
|
-
return redirect(url)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def robohash(identifier, size):
|
|
120
|
-
"""
|
|
121
|
-
Robohash provider
|
|
122
|
-
|
|
123
|
-
Redirect to the Robohash API
|
|
124
|
-
with parameters extracted from configuration.
|
|
125
|
-
|
|
126
|
-
See: https://robohash.org/
|
|
127
|
-
"""
|
|
128
|
-
skin = get_config("robohash_skin")
|
|
129
|
-
background = get_config("robohash_background")
|
|
130
|
-
url = ROBOHASH_URL.format(identifier=identifier, size=size, skin=skin, bg=background)
|
|
131
|
-
return redirect(url)
|
|
File without changes
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
from udata.features.identicon.backends import internal
|
|
2
|
-
from udata.tests.api import PytestOnlyAPITestCase
|
|
3
|
-
from udata.tests.helpers import assert200
|
|
4
|
-
from udata.utils import faker
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class InternalBackendTest(PytestOnlyAPITestCase):
|
|
8
|
-
def test_base_rendering(self):
|
|
9
|
-
response = internal(faker.word(), 32)
|
|
10
|
-
assert200(response)
|
|
11
|
-
assert response.mimetype == "image/png"
|
|
12
|
-
assert response.is_streamed
|
|
13
|
-
etag, weak = response.get_etag()
|
|
14
|
-
assert etag is not None
|
|
15
|
-
|
|
16
|
-
def test_render_twice_the_same(self):
|
|
17
|
-
identifier = faker.word()
|
|
18
|
-
self.assertStreamEqual(internal(identifier, 32), internal(identifier, 32))
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
from flask import current_app
|
|
2
|
-
|
|
3
|
-
from udata.models import GeoZone, db
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def check_for_territories(query):
|
|
7
|
-
"""
|
|
8
|
-
Return a geozone queryset of territories given the `query`.
|
|
9
|
-
|
|
10
|
-
Results are sorted by population and area (biggest first).
|
|
11
|
-
"""
|
|
12
|
-
if not query or not current_app.config.get("ACTIVATE_TERRITORIES"):
|
|
13
|
-
return []
|
|
14
|
-
|
|
15
|
-
dbqs = db.Q()
|
|
16
|
-
query = query.lower()
|
|
17
|
-
is_digit = query.isdigit()
|
|
18
|
-
query_length = len(query)
|
|
19
|
-
for level in current_app.config.get("HANDLED_LEVELS"):
|
|
20
|
-
if level == "country":
|
|
21
|
-
continue # Level not fully handled yet.
|
|
22
|
-
q = db.Q(level=level)
|
|
23
|
-
if query_length == 2 and level == "fr:departement" and (is_digit or query in ("2a", "2b")):
|
|
24
|
-
# Counties + Corsica.
|
|
25
|
-
q &= db.Q(code=query)
|
|
26
|
-
elif query_length == 3 and level == "fr:departement" and is_digit:
|
|
27
|
-
# French DROM-COM.
|
|
28
|
-
q &= db.Q(code=query)
|
|
29
|
-
elif (
|
|
30
|
-
query_length == 5
|
|
31
|
-
and level == "fr:commune"
|
|
32
|
-
and (is_digit or query.startswith("2a") or query.startswith("2b"))
|
|
33
|
-
):
|
|
34
|
-
# INSEE code then postal codes with Corsica exceptions.
|
|
35
|
-
q &= db.Q(code=query)
|
|
36
|
-
elif query_length >= 4:
|
|
37
|
-
# Check names starting with query or exact match.
|
|
38
|
-
q &= db.Q(name__istartswith=query) | db.Q(name__iexact=query)
|
|
39
|
-
else:
|
|
40
|
-
continue
|
|
41
|
-
|
|
42
|
-
# Meta Q object, ready to be passed to a queryset.
|
|
43
|
-
dbqs |= q
|
|
44
|
-
|
|
45
|
-
if dbqs.empty:
|
|
46
|
-
return []
|
|
47
|
-
|
|
48
|
-
# Sort matching results by population and area.
|
|
49
|
-
return GeoZone.objects(dbqs).order_by("-population", "-area")
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
from udata.api import API, api
|
|
2
|
-
from udata.features.territories import check_for_territories
|
|
3
|
-
|
|
4
|
-
suggest_parser = api.parser()
|
|
5
|
-
suggest_parser.add_argument(
|
|
6
|
-
"q", type=str, help="The string to autocomplete/suggest", location="args", required=True
|
|
7
|
-
)
|
|
8
|
-
suggest_parser.add_argument(
|
|
9
|
-
"size", type=int, help="The maximum result size", location="args", required=False
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@api.route("/territory/suggest/", endpoint="suggest_territory")
|
|
14
|
-
class SuggestTerritoriesAPI(API):
|
|
15
|
-
@api.doc("suggest_territory")
|
|
16
|
-
@api.expect(suggest_parser)
|
|
17
|
-
def get(self):
|
|
18
|
-
args = suggest_parser.parse_args()
|
|
19
|
-
territories = check_for_territories(args["q"])
|
|
20
|
-
if args["size"]:
|
|
21
|
-
territories = territories[: args["size"]]
|
|
22
|
-
return [
|
|
23
|
-
{"id": territory.id, "title": territory.name, "page": territory.external_url}
|
|
24
|
-
for territory in territories
|
|
25
|
-
]
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
from udata.models import License, Organization
|
|
2
|
-
|
|
3
|
-
__all__ = ("TerritoryDataset", "ResourceBasedTerritoryDataset", "TERRITORY_DATASETS")
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
TERRITORY_DATASETS = {"commune": {}, "departement": {}, "region": {}, "country": {}}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class TerritoryDataset(object):
|
|
10
|
-
order = 0
|
|
11
|
-
id = ""
|
|
12
|
-
title = ""
|
|
13
|
-
organization_id = ""
|
|
14
|
-
url_template = ""
|
|
15
|
-
description = ""
|
|
16
|
-
license_id = "fr-lo"
|
|
17
|
-
|
|
18
|
-
def __init__(self, territory):
|
|
19
|
-
self.territory = territory
|
|
20
|
-
|
|
21
|
-
@property
|
|
22
|
-
def url(self):
|
|
23
|
-
return self.url_template.format(code=self.territory.code)
|
|
24
|
-
|
|
25
|
-
@property
|
|
26
|
-
def slug(self):
|
|
27
|
-
return "{territory_id}:{id}".format(territory_id=self.territory.id, id=self.id)
|
|
28
|
-
|
|
29
|
-
@property
|
|
30
|
-
def organization(self):
|
|
31
|
-
return Organization.objects.get(id=self.organization_id)
|
|
32
|
-
|
|
33
|
-
@property
|
|
34
|
-
def license(self):
|
|
35
|
-
return License.objects(id=self.license_id).first()
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class ResourceBasedTerritoryDataset(TerritoryDataset):
|
|
39
|
-
dataset_id = ""
|
|
40
|
-
resource_id = ""
|
|
41
|
-
territory_attr = ""
|
|
42
|
-
csv_column = ""
|
|
43
|
-
|
|
44
|
-
def url_for(self, external=False):
|
|
45
|
-
return None
|
|
46
|
-
|
|
47
|
-
url = property(url_for)
|
|
48
|
-
|
|
49
|
-
@property
|
|
50
|
-
def external_url(self):
|
|
51
|
-
return self.url_for(external=True)
|
udata/flask_mongoengine/json.py
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
from bson import json_util
|
|
2
|
-
from flask.json import JSONEncoder
|
|
3
|
-
from mongoengine.base import BaseDocument
|
|
4
|
-
from mongoengine.queryset import QuerySet
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def _make_encoder(superclass):
|
|
8
|
-
class MongoEngineJSONEncoder(superclass):
|
|
9
|
-
"""
|
|
10
|
-
A JSONEncoder which provides serialization of MongoEngine
|
|
11
|
-
documents and queryset objects.
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
def default(self, obj):
|
|
15
|
-
if isinstance(obj, BaseDocument):
|
|
16
|
-
return json_util._json_convert(obj.to_mongo())
|
|
17
|
-
elif isinstance(obj, QuerySet):
|
|
18
|
-
return json_util._json_convert(obj.as_pymongo())
|
|
19
|
-
return superclass.default(self, obj)
|
|
20
|
-
|
|
21
|
-
return MongoEngineJSONEncoder
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
MongoEngineJSONEncoder = _make_encoder(JSONEncoder)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def override_json_encoder(app):
|
|
28
|
-
"""
|
|
29
|
-
A function to dynamically create a new MongoEngineJSONEncoder class
|
|
30
|
-
based upon a custom base class.
|
|
31
|
-
This function allows us to combine MongoEngine serialization with
|
|
32
|
-
any changes to Flask's JSONEncoder which a user may have made
|
|
33
|
-
prior to calling init_app.
|
|
34
|
-
|
|
35
|
-
NOTE: This does not cover situations where users override
|
|
36
|
-
an instance's json_encoder after calling init_app.
|
|
37
|
-
"""
|
|
38
|
-
app.json_encoder = _make_encoder(app.json_encoder)
|