• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Problematy / goodmap / 21284115567

23 Jan 2026 11:10AM UTC coverage: 97.152%. First build
21284115567

Pull #324

github

web-flow
Merge 2403aa344 into 0778a150c
Pull Request #324: feat: images support introduced

27 of 62 new or added lines in 2 files covered. (43.55%)

1501 of 1545 relevant lines covered (97.15%)

0.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.72
/goodmap/goodmap.py
1
"""Goodmap engine with location management and admin interface."""
2

3
import logging
1✔
4
import os
1✔
5

6
from flask import Blueprint, redirect, render_template, session
1✔
7
from flask_wtf.csrf import CSRFProtect, generate_csrf
1✔
8
from platzky import platzky
1✔
9
from platzky.attachment import AttachmentProtocol, create_attachment_class
1✔
10
from platzky.config import AttachmentConfig, languages_dict
1✔
11
from platzky.models import CmsModule
1✔
12

13
from goodmap.admin_api import admin_pages
1✔
14
from goodmap.config import GoodmapConfig
1✔
15
from goodmap.core_api import core_pages
1✔
16
from goodmap.data_models.location import create_location_model
1✔
17
from goodmap.db import (
1✔
18
    extend_db_with_goodmap_queries,
19
    get_location_obligatory_fields,
20
)
21

22
logger = logging.getLogger(__name__)
1✔
23

24

25
def create_app(config_path: str) -> platzky.Engine:
1✔
26
    """Create Goodmap application from YAML configuration file.
27

28
    Args:
29
        config_path: Path to YAML configuration file
30

31
    Returns:
32
        platzky.Engine: Configured Flask application
33
    """
34
    config = GoodmapConfig.parse_yaml(config_path)
1✔
35
    return create_app_from_config(config)
1✔
36

37

38
# TODO Checking if there is a feature flag secition should be part of configs logic not client app
39
def is_feature_enabled(config: GoodmapConfig, feature: str) -> bool:
1✔
40
    """Check if a feature flag is enabled in the configuration.
41

42
    Args:
43
        config: Goodmap configuration object
44
        feature: Name of the feature flag to check
45

46
    Returns:
47
        bool: True if feature is enabled, False otherwise
48
    """
49
    return config.feature_flags.get(feature, False) if config.feature_flags else False
1✔
50

51

52
def create_app_from_config(config: GoodmapConfig) -> platzky.Engine:
1✔
53
    """Create and configure Goodmap application from config object.
54

55
    Sets up location models, database queries, CSRF protection, API blueprints,
56
    and admin interface based on the provided configuration.
57

58
    Args:
59
        config: Goodmap configuration object
60

61
    Returns:
62
        platzky.Engine: Fully configured Flask application with Goodmap features
63
    """
64
    # Configure debug logging (FLASK_DEBUG is set by `flask --debug` before factory runs)
65
    # Must be done before platzky.create_app_from_config to capture plugin init logs
66
    if os.environ.get("FLASK_DEBUG") == "1":
1✔
NEW
67
        handler = logging.StreamHandler()
×
NEW
68
        handler.setLevel(logging.DEBUG)
×
NEW
69
        handler.setFormatter(logging.Formatter("%(name)s - %(levelname)s - %(message)s"))
×
NEW
70
        for logger_name in ("goodmap", "platzky"):
×
NEW
71
            logging.getLogger(logger_name).setLevel(logging.DEBUG)
×
NEW
72
            logging.getLogger(logger_name).addHandler(handler)
×
NEW
73
        logger.info("Debug logging enabled")
×
74

75
    directory = os.path.dirname(os.path.realpath(__file__))
1✔
76

77
    locale_dir = os.path.join(directory, "locale")
1✔
78
    config.translation_directories.append(locale_dir)
1✔
79
    app = platzky.create_app_from_config(config)
1✔
80

81
    # SECURITY: Set maximum request body size to 100KB (prevents memory exhaustion)
82
    # This protects against large file uploads and JSON payloads
83
    # Based on calculation: ~6.5KB max legitimate payload + multipart overhead
84
    if "MAX_CONTENT_LENGTH" not in app.config:
1✔
85
        app.config["MAX_CONTENT_LENGTH"] = 100 * 1024  # 100KB
1✔
86

87
    if is_feature_enabled(config, "USE_LAZY_LOADING"):
1✔
88
        location_obligatory_fields = get_location_obligatory_fields(app.db)
1✔
89
        # Extend db with goodmap queries first so we can use the bound method
90
        location_model = create_location_model(location_obligatory_fields, {})
1✔
91
        app.db = extend_db_with_goodmap_queries(app.db, location_model)
1✔
92

93
        # Use the extended db method directly (already bound by extend_db_with_goodmap_queries)
94
        try:
1✔
95
            category_data = app.db.get_category_data()  # type: ignore[attr-defined]
1✔
96
            categories = category_data.get("categories", {})
1✔
97
        except (KeyError, AttributeError):
1✔
98
            # Handle case where categories don't exist in the data
99
            categories = {}
1✔
100

101
        # Recreate location model with categories if we got them
102
        if categories:
1✔
103
            location_model = create_location_model(location_obligatory_fields, categories)
1✔
104
            app.db = extend_db_with_goodmap_queries(app.db, location_model)
1✔
105
    else:
106
        location_obligatory_fields = []
1✔
107
        categories = {}
1✔
108
        location_model = create_location_model(location_obligatory_fields, categories)
1✔
109
        app.db = extend_db_with_goodmap_queries(app.db, location_model)
1✔
110

111
    app.extensions["goodmap"] = {"location_obligatory_fields": location_obligatory_fields}
1✔
112

113
    CSRFProtect(app)
1✔
114

115
    # Create JPEG-only Attachment class for photo uploads
116
    photo_attachment_config = AttachmentConfig(
1✔
117
        allowed_mime_types=frozenset({"image/jpeg"}),
118
        allowed_extensions=frozenset({"jpg", "jpeg"}),
119
    )
120
    PhotoAttachment = create_attachment_class(photo_attachment_config)
1✔
121

122
    # Wrap notifier with detailed logging for debugging
123
    original_notify = app.notify
1✔
124

125
    def debug_notify(message: str, attachments: list[AttachmentProtocol] | None = None):
1✔
126
        logger.debug("Notifier called with message: %s", message)
1✔
127
        if attachments:
1✔
NEW
128
            logger.debug(
×
129
                "Attachments: %s",
130
                [(a.filename, len(a.content), a.mime_type) for a in attachments],
131
            )
132
        try:
1✔
133
            result = original_notify(message, attachments=attachments)
1✔
134
            logger.debug("Notifier succeeded, result type: %s", type(result).__name__)
1✔
135
            return result
1✔
NEW
136
        except Exception as e:
×
NEW
137
            logger.error(
×
138
                "Notifier failed: %s: %s",
139
                type(e).__name__,
140
                str(e),
141
                exc_info=True,
142
            )
NEW
143
            raise
×
144

145
    cp = core_pages(
1✔
146
        app.db,
147
        languages_dict(config.languages),
148
        debug_notify,
149
        generate_csrf,
150
        location_model,
151
        photo_attachment_class=PhotoAttachment,
152
        feature_flags=config.feature_flags,
153
    )
154
    app.register_blueprint(cp)
1✔
155

156
    goodmap = Blueprint("goodmap", __name__, url_prefix="/", template_folder="templates")
1✔
157

158
    @goodmap.route("/")
1✔
159
    def index():
1✔
160
        """Render main map interface with location schema.
161

162
        Prepares and passes location schema including obligatory fields and
163
        categories to the frontend for dynamic form generation.
164

165
        Returns:
166
            Rendered map.html template with feature flags and location schema
167
        """
168
        # Prepare location schema for frontend dynamic forms
169
        # Include full schema from Pydantic model for better type information
170
        category_data = app.db.get_category_data()  # type: ignore[attr-defined]
1✔
171
        categories = category_data.get("categories", {})
1✔
172

173
        # Get full JSON schema from Pydantic model
174
        model_json_schema = location_model.model_json_schema()
1✔
175
        properties = model_json_schema.get("properties", {})
1✔
176

177
        # Filter out uuid and position from properties for frontend form
178
        form_fields = {
1✔
179
            name: spec for name, spec in properties.items() if name not in ("uuid", "position")
180
        }
181

182
        location_schema = {  # TODO remove backward compatibility - deprecation
1✔
183
            "obligatory_fields": app.extensions["goodmap"][
184
                "location_obligatory_fields"
185
            ],  # Backward compatibility
186
            "categories": categories,  # Backward compatibility
187
            "fields": form_fields,
188
        }
189

190
        return render_template(
1✔
191
            "map.html",
192
            feature_flags=config.feature_flags,
193
            goodmap_frontend_lib_url=config.goodmap_frontend_lib_url,
194
            location_schema=location_schema,
195
        )
196

197
    @goodmap.route("/goodmap-admin")
1✔
198
    def admin():
1✔
199
        """Render admin interface for managing map data.
200

201
        Requires user to be logged in (redirects to /admin if not).
202
        Provides admin panel for managing locations, suggestions, and reports.
203
        Only available when ENABLE_ADMIN_PANEL feature flag is enabled.
204

205
        Returns:
206
            Rendered goodmap-admin.html template or redirect to login
207
        """
208
        if not is_feature_enabled(config, "ENABLE_ADMIN_PANEL"):
×
209
            return redirect("/")
×
210

211
        user = session.get("user", None)
×
212
        if not user:
×
213
            return redirect("/admin")
×
214

215
        # TODO: This should be replaced with a proper user authentication check,
216
        #       cms_modules should be passed from the app
217
        return render_template(
×
218
            "goodmap-admin.html",
219
            feature_flags=config.feature_flags,
220
            goodmap_frontend_lib_url=config.goodmap_frontend_lib_url,
221
            user=user,
222
            cms_modules=app.cms_modules,
223
        )
224

225
    app.register_blueprint(goodmap)
1✔
226

227
    if is_feature_enabled(config, "ENABLE_ADMIN_PANEL"):
1✔
228
        admin_bp = admin_pages(app.db, location_model)
1✔
229
        app.register_blueprint(admin_bp)
1✔
230

231
        goodmap_cms_modules = CmsModule.model_validate(
1✔
232
            {
233
                "name": "Map admin panel",
234
                "description": "Admin panel for managing map data",
235
                "slug": "goodmap-admin",
236
                "template": "goodmap-admin.html",
237
            }
238
        )
239
        app.add_cms_module(goodmap_cms_modules)
1✔
240

241
    return app
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc