goodmap 1.1.7__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.
- goodmap/__init__.py +1 -0
- goodmap/clustering.py +75 -0
- goodmap/config.py +42 -0
- goodmap/core.py +46 -0
- goodmap/core_api.py +467 -0
- goodmap/data_models/location.py +68 -0
- goodmap/data_validator.py +119 -0
- goodmap/db.py +1466 -0
- goodmap/exceptions.py +100 -0
- goodmap/formatter.py +27 -0
- goodmap/goodmap.py +89 -0
- goodmap/templates/goodmap-admin.html +743 -0
- goodmap/templates/map.html +124 -0
- goodmap-1.1.7.dist-info/METADATA +142 -0
- goodmap-1.1.7.dist-info/RECORD +17 -0
- goodmap-1.1.7.dist-info/WHEEL +4 -0
- goodmap-1.1.7.dist-info/licenses/LICENSE.md +21 -0
goodmap/exceptions.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Custom exceptions for Goodmap application."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import uuid as uuid_lib
|
|
5
|
+
|
|
6
|
+
from pydantic import ValidationError as PydanticValidationError
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _sanitize_uuid(uuid: str | None) -> str:
|
|
12
|
+
"""Validate and sanitize UUID to prevent injection attacks."""
|
|
13
|
+
if uuid is None:
|
|
14
|
+
return "<unknown>"
|
|
15
|
+
try:
|
|
16
|
+
# Validate UUID format
|
|
17
|
+
uuid_lib.UUID(uuid)
|
|
18
|
+
return uuid
|
|
19
|
+
except (ValueError, AttributeError, TypeError):
|
|
20
|
+
logger.warning("Invalid UUID format detected", extra={"raw_uuid": repr(uuid)})
|
|
21
|
+
return "<invalid-uuid>"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class GoodmapError(Exception):
|
|
25
|
+
"""Base exception for all Goodmap errors."""
|
|
26
|
+
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ValidationError(GoodmapError):
|
|
31
|
+
"""Base validation error."""
|
|
32
|
+
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class LocationValidationError(ValidationError):
|
|
37
|
+
"""Validation error for location data with enhanced context."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, validation_error: PydanticValidationError, uuid: str | None = None):
|
|
40
|
+
self.uuid = _sanitize_uuid(uuid)
|
|
41
|
+
self.original_error = validation_error
|
|
42
|
+
self.validation_errors = validation_error.errors()
|
|
43
|
+
super().__init__(str(validation_error))
|
|
44
|
+
|
|
45
|
+
def __str__(self):
|
|
46
|
+
# Don't expose error details in string representation
|
|
47
|
+
if self.uuid and self.uuid not in ("<unknown>", "<invalid-uuid>"):
|
|
48
|
+
return f"Validation failed for location '{self.uuid}'"
|
|
49
|
+
return "Validation failed"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class NotFoundError(GoodmapError):
|
|
53
|
+
"""Resource not found error."""
|
|
54
|
+
|
|
55
|
+
def __init__(self, uuid: str, resource_type: str = "Resource"):
|
|
56
|
+
self.uuid = _sanitize_uuid(uuid)
|
|
57
|
+
super().__init__(f"{resource_type} with uuid '{self.uuid}' not found")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class LocationNotFoundError(NotFoundError):
|
|
61
|
+
"""Location with specified UUID not found."""
|
|
62
|
+
|
|
63
|
+
def __init__(self, uuid: str):
|
|
64
|
+
super().__init__(uuid, "Location")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class AlreadyExistsError(GoodmapError):
|
|
68
|
+
"""Resource already exists error."""
|
|
69
|
+
|
|
70
|
+
def __init__(self, uuid: str, resource_type: str = "Resource"):
|
|
71
|
+
self.uuid = _sanitize_uuid(uuid)
|
|
72
|
+
super().__init__(f"{resource_type} with uuid '{self.uuid}' already exists")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class LocationAlreadyExistsError(AlreadyExistsError):
|
|
76
|
+
"""Location with specified UUID already exists."""
|
|
77
|
+
|
|
78
|
+
def __init__(self, uuid: str):
|
|
79
|
+
super().__init__(uuid, "Location")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class SuggestionNotFoundError(NotFoundError):
|
|
83
|
+
"""Suggestion with specified UUID not found."""
|
|
84
|
+
|
|
85
|
+
def __init__(self, uuid: str):
|
|
86
|
+
super().__init__(uuid, "Suggestion")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class SuggestionAlreadyExistsError(AlreadyExistsError):
|
|
90
|
+
"""Suggestion with specified UUID already exists."""
|
|
91
|
+
|
|
92
|
+
def __init__(self, uuid: str):
|
|
93
|
+
super().__init__(uuid, "Suggestion")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class ReportNotFoundError(NotFoundError):
|
|
97
|
+
"""Report with specified UUID not found."""
|
|
98
|
+
|
|
99
|
+
def __init__(self, uuid: str):
|
|
100
|
+
super().__init__(uuid, "Report")
|
goodmap/formatter.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from flask_babel import gettext, lazy_gettext
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def safe_gettext(text):
|
|
5
|
+
if isinstance(text, list):
|
|
6
|
+
return list(map(gettext, text))
|
|
7
|
+
elif isinstance(text, dict):
|
|
8
|
+
return text
|
|
9
|
+
else:
|
|
10
|
+
return gettext(text)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def prepare_pin(place, visible_fields, meta_data):
|
|
14
|
+
pin_data = {
|
|
15
|
+
"title": place["name"],
|
|
16
|
+
"subtitle": lazy_gettext(place["type_of_place"]), # TODO this should not be obligatory
|
|
17
|
+
"position": place["position"],
|
|
18
|
+
"metadata": {
|
|
19
|
+
gettext(field): safe_gettext(place[field]) for field in meta_data if field in place
|
|
20
|
+
},
|
|
21
|
+
"data": [
|
|
22
|
+
[gettext(field), safe_gettext(place[field])]
|
|
23
|
+
for field in visible_fields
|
|
24
|
+
if field in place
|
|
25
|
+
],
|
|
26
|
+
}
|
|
27
|
+
return pin_data
|
goodmap/goodmap.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from flask import Blueprint, redirect, render_template, session
|
|
4
|
+
from flask_wtf.csrf import CSRFProtect, generate_csrf
|
|
5
|
+
from platzky import platzky
|
|
6
|
+
from platzky.config import languages_dict
|
|
7
|
+
from platzky.models import CmsModule
|
|
8
|
+
|
|
9
|
+
from goodmap.config import GoodmapConfig
|
|
10
|
+
from goodmap.core_api import core_pages
|
|
11
|
+
from goodmap.data_models.location import create_location_model
|
|
12
|
+
from goodmap.db import extend_db_with_goodmap_queries, get_location_obligatory_fields
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def create_app(config_path: str) -> platzky.Engine:
|
|
16
|
+
config = GoodmapConfig.parse_yaml(config_path)
|
|
17
|
+
return create_app_from_config(config)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# TODO Checking if there is a feature flag secition should be part of configs logic not client app
|
|
21
|
+
def is_feature_enabled(config: GoodmapConfig, feature: str) -> bool:
|
|
22
|
+
return config.feature_flags.get(feature, False) if config.feature_flags else False
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def create_app_from_config(config: GoodmapConfig) -> platzky.Engine:
|
|
26
|
+
directory = os.path.dirname(os.path.realpath(__file__))
|
|
27
|
+
|
|
28
|
+
locale_dir = os.path.join(directory, "locale")
|
|
29
|
+
config.translation_directories.append(locale_dir)
|
|
30
|
+
app = platzky.create_app_from_config(config)
|
|
31
|
+
|
|
32
|
+
if is_feature_enabled(config, "USE_LAZY_LOADING"):
|
|
33
|
+
location_obligatory_fields = get_location_obligatory_fields(app.db)
|
|
34
|
+
else:
|
|
35
|
+
location_obligatory_fields = []
|
|
36
|
+
|
|
37
|
+
location_model = create_location_model(location_obligatory_fields)
|
|
38
|
+
|
|
39
|
+
app.db = extend_db_with_goodmap_queries(app.db, location_model)
|
|
40
|
+
|
|
41
|
+
CSRFProtect(app)
|
|
42
|
+
|
|
43
|
+
cp = core_pages(
|
|
44
|
+
app.db,
|
|
45
|
+
languages_dict(config.languages),
|
|
46
|
+
app.notify,
|
|
47
|
+
generate_csrf,
|
|
48
|
+
location_model,
|
|
49
|
+
feature_flags=config.feature_flags,
|
|
50
|
+
)
|
|
51
|
+
app.register_blueprint(cp)
|
|
52
|
+
goodmap = Blueprint("goodmap", __name__, url_prefix="/", template_folder="templates")
|
|
53
|
+
|
|
54
|
+
@goodmap.route("/")
|
|
55
|
+
def index():
|
|
56
|
+
return render_template(
|
|
57
|
+
"map.html",
|
|
58
|
+
feature_flags=config.feature_flags,
|
|
59
|
+
goodmap_frontend_lib_url=config.goodmap_frontend_lib_url,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
@goodmap.route("/goodmap-admin")
|
|
63
|
+
def admin():
|
|
64
|
+
user = session.get("user", None)
|
|
65
|
+
if not user:
|
|
66
|
+
return redirect("/admin")
|
|
67
|
+
|
|
68
|
+
# TODO: This should be replaced with a proper user authentication check,
|
|
69
|
+
# cms_modules should be passed from the app
|
|
70
|
+
return render_template(
|
|
71
|
+
"goodmap-admin.html",
|
|
72
|
+
feature_flags=config.feature_flags,
|
|
73
|
+
goodmap_frontend_lib_url=config.goodmap_frontend_lib_url,
|
|
74
|
+
user=user,
|
|
75
|
+
cms_modules=app.cms_modules,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
app.register_blueprint(goodmap)
|
|
79
|
+
goodmap_cms_modules = CmsModule.model_validate(
|
|
80
|
+
{
|
|
81
|
+
"name": "Map admin panel",
|
|
82
|
+
"description": "Admin panel for managing map data",
|
|
83
|
+
"slug": "goodmap-admin",
|
|
84
|
+
"template": "goodmap-admin.html",
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
app.add_cms_module(goodmap_cms_modules)
|
|
88
|
+
|
|
89
|
+
return app
|