goodmap 1.5.0__py3-none-any.whl → 1.6.0__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/api_models.py CHANGED
@@ -14,7 +14,9 @@ class LocationReportRequest(BaseModel):
14
14
  """Request model for reporting a location issue."""
15
15
 
16
16
  id: str = Field(..., description="Location UUID to report")
17
- description: str = Field(..., min_length=1, description="Description of the problem")
17
+ description: str = Field(
18
+ ..., min_length=1, max_length=500, description="Description of the problem"
19
+ )
18
20
 
19
21
 
20
22
  class LocationReportResponse(BaseModel):
goodmap/core_api.py CHANGED
@@ -40,14 +40,29 @@ MAX_ZOOM = 16
40
40
  CLUSTER_RADIUS = 200
41
41
  CLUSTER_EXTENT = 512
42
42
 
43
+ # Report description validation constants
44
+ MAX_DESCRIPTION_LENGTH = 500
45
+
43
46
  # Error message constants
44
47
  ERROR_INVALID_REQUEST_DATA = "Invalid request data"
45
48
  ERROR_INVALID_LOCATION_DATA = "Invalid location data"
46
49
  ERROR_LOCATION_NOT_FOUND = "Location not found"
50
+ ERROR_INVALID_DESCRIPTION = "Invalid report description"
47
51
 
48
52
  logger = logging.getLogger(__name__)
49
53
 
50
54
 
55
+ @deprecation.deprecated(
56
+ deprecated_in="1.5.0",
57
+ removed_in="2.0.0",
58
+ details="Configure 'reported_issue_types' in the database instead. "
59
+ "The hardcoded fallback will be removed in a future release.",
60
+ )
61
+ def get_default_issue_options():
62
+ """Return hardcoded fallback issue options for backward compatibility."""
63
+ return ["notHere", "overload", "broken", "other"]
64
+
65
+
51
66
  def make_tuple_translation(keys_to_translate):
52
67
  return [(x, gettext(x)) for x in keys_to_translate]
53
68
 
@@ -249,10 +264,23 @@ def core_pages(
249
264
  """
250
265
  try:
251
266
  location_report = request.get_json()
267
+ description = location_report["description"]
268
+
269
+ # Validate description against configured issue options
270
+ issue_options = database.get_issue_options()
271
+ if not issue_options:
272
+ issue_options = get_default_issue_options()
273
+
274
+ if description not in issue_options:
275
+ if "other" not in issue_options:
276
+ return make_response(jsonify({"message": ERROR_INVALID_DESCRIPTION}), 400)
277
+ if len(description) > MAX_DESCRIPTION_LENGTH:
278
+ return make_response(jsonify({"message": ERROR_INVALID_DESCRIPTION}), 400)
279
+
252
280
  report = {
253
281
  "uuid": str(uuid.uuid4()),
254
282
  "location_id": location_report["id"],
255
- "description": location_report["description"],
283
+ "description": description,
256
284
  "status": "pending",
257
285
  "priority": "medium",
258
286
  }
goodmap/db.py CHANGED
@@ -335,6 +335,34 @@ def get_location_obligatory_fields(db):
335
335
  return globals()[f"{db.module_name}_get_location_obligatory_fields"](db)
336
336
 
337
337
 
338
+ # ------------------------------------------------
339
+ # get_issue_options
340
+
341
+
342
+ def json_db_get_issue_options(self):
343
+ return self.data.get("reported_issue_types", [])
344
+
345
+
346
+ def json_file_db_get_issue_options(self):
347
+ with open(self.data_file_path, "r") as file:
348
+ return json.load(file)["map"].get("reported_issue_types", [])
349
+
350
+
351
+ def google_json_db_get_issue_options(self):
352
+ return self.data.get("map", {}).get("reported_issue_types", [])
353
+
354
+
355
+ def mongodb_db_get_issue_options(self):
356
+ config_doc = self.db.config.find_one({"_id": "map_config"})
357
+ if config_doc and "reported_issue_types" in config_doc:
358
+ return config_doc["reported_issue_types"]
359
+ return []
360
+
361
+
362
+ def get_issue_options(db):
363
+ return globals()[f"{db.module_name}_get_issue_options"]
364
+
365
+
338
366
  # ------------------------------------------------
339
367
  # get_data
340
368
 
@@ -1440,6 +1468,7 @@ def delete_report(db, report_id):
1440
1468
 
1441
1469
 
1442
1470
  def extend_db_with_goodmap_queries(db, location_model):
1471
+ db.extend("get_issue_options", get_issue_options(db))
1443
1472
  db.extend("get_data", get_data(db))
1444
1473
  db.extend("get_visible_data", get_visible_data(db))
1445
1474
  db.extend("get_meta_data", get_meta_data(db))
goodmap/goodmap.py CHANGED
@@ -4,6 +4,7 @@ import logging
4
4
  import os
5
5
 
6
6
  from flask import Blueprint, redirect, render_template, session
7
+ from flask_babel import gettext
7
8
  from flask_wtf.csrf import CSRFProtect, generate_csrf
8
9
  from platzky import platzky
9
10
  from platzky.attachment import create_attachment_class
@@ -136,12 +137,16 @@ def create_app_from_config(config: GoodmapConfig) -> platzky.Engine:
136
137
  name: spec for name, spec in properties.items() if name not in ("uuid", "position")
137
138
  }
138
139
 
140
+ issue_options_raw = app.db.get_issue_options() # type: ignore[attr-defined]
141
+ reported_issue_types = [{"value": t, "label": gettext(t)} for t in issue_options_raw]
142
+
139
143
  location_schema = { # TODO remove backward compatibility - deprecation
140
144
  "obligatory_fields": app.extensions["goodmap"][
141
145
  "location_obligatory_fields"
142
146
  ], # Backward compatibility
143
147
  "categories": categories, # Backward compatibility
144
148
  "fields": form_fields,
149
+ "reported_issue_types": reported_issue_types,
145
150
  }
146
151
 
147
152
  return render_template(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: goodmap
3
- Version: 1.5.0
3
+ Version: 1.6.0
4
4
  Summary: Map engine to serve all the people :)
5
5
  Author: Krzysztof Kolodzinski
6
6
  Author-email: krzysztof.kolodzinski@problematy.pl
@@ -1,21 +1,20 @@
1
1
  goodmap/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
2
2
  goodmap/admin_api.py,sha256=5tvHeqknG8WmhBYmIHlQHTOUA-zaT8FKaAyyLdvX2EE,10290
3
- goodmap/api_models.py,sha256=Bv4OTGuckNneCrxaQ1Y_PMeu7YFLvGUqU2EorvDlUjY,3438
3
+ goodmap/api_models.py,sha256=drE0i6AWHat7siwTpnvpSrWVSC5uW0pTKQzo68xwb34,3468
4
4
  goodmap/clustering.py,sha256=ULB-fPNOUDblgpBK4vzuo0o2yqIcvG84F3R6Za2X_l4,2905
5
5
  goodmap/config.py,sha256=CsmC1zuvVab90VW50dtARHbFJpy2vfsIfbque8Zgc-U,1313
6
6
  goodmap/core.py,sha256=AgdGLfeJvL7TlTX893NR2YdCS8EuXx93Gx6ndvWws7s,2673
7
- goodmap/core_api.py,sha256=CnQHXzSxym-URHE4teVkyOAT-iIMWWEX0cP7R_01Tg4,19981
7
+ goodmap/core_api.py,sha256=ao80Tynclv6IV51hXMPA0W3RQUwJPwxZFXcs-H287KE,21105
8
8
  goodmap/data_models/location.py,sha256=_I27R06ovEL9ctv_SZ3yoLL-RwmyE3VDsVOG4a89q50,6798
9
- goodmap/data_validator.py,sha256=lBmVAPxvSmEOdUGeVYSjUvVVmKfPyq4CWoHfczTtEMM,4090
10
- goodmap/db.py,sha256=TcqYGbK5yk6S735Si1AzjNqcbB1nsd9pFGOy5qN9Vec,46589
9
+ goodmap/db.py,sha256=ZXkonYpiWwtCGsOk9XAXlaNk_db4ZGrJjgPspBdm9gc,47408
11
10
  goodmap/exceptions.py,sha256=jkFAUoc5LHk8iPjxHxbcRp8W6qFCSEA25A8XaSwxwyo,2906
12
11
  goodmap/feature_flags.py,sha256=-hiqTX4OlhfY_4M1Kvy-_z1Fx6YTaFi3SVGYa0Pamcw,334
13
12
  goodmap/formatter.py,sha256=4rqcg9A9Y9opAi7eb8kMDdUC03M3uzZgCxx29cvvIag,1403
14
- goodmap/goodmap.py,sha256=f69aUloRe4bpx2JRwZFiHOeUSk0Exq-Qv2FCdwiwLA0,7541
13
+ goodmap/goodmap.py,sha256=Hi8np9oKkPzeRFYVNPiYvhW1chsfJgIoGwUuhZ_3Q-g,7811
15
14
  goodmap/json_security.py,sha256=EHAxNlb16AVwphgf4F7yObtMZpbR9M538dwn_STRcMo,3275
16
15
  goodmap/templates/goodmap-admin.html,sha256=LSiOZ9-n29CnlfVNwdgmXwT7Xe7t5gvGh1xSrFGqOIY,35669
17
16
  goodmap/templates/map.html,sha256=Uk7FFrZwvHZvG0DDaQrGW5ZrIMD21XrJzMub76uIlAg,4348
18
- goodmap-1.5.0.dist-info/LICENSE.md,sha256=nkCQOR7uheLRvHRfXmwx9LhBnMcPeBU9d4ebLojDiQU,1067
19
- goodmap-1.5.0.dist-info/METADATA,sha256=PpLVvzHAcIIKEUVAqBkTZ1zcLXNnFFUivPUFIieiu6k,5798
20
- goodmap-1.5.0.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
21
- goodmap-1.5.0.dist-info/RECORD,,
17
+ goodmap-1.6.0.dist-info/LICENSE.md,sha256=nkCQOR7uheLRvHRfXmwx9LhBnMcPeBU9d4ebLojDiQU,1067
18
+ goodmap-1.6.0.dist-info/METADATA,sha256=ljeI70gs0U8RV8dm-VDX8Vt4MHy5_KxfB7tlik3l-0E,5798
19
+ goodmap-1.6.0.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
20
+ goodmap-1.6.0.dist-info/RECORD,,
goodmap/data_validator.py DELETED
@@ -1,119 +0,0 @@
1
- import json
2
- from enum import Enum
3
- from sys import argv, stderr
4
-
5
-
6
- class ViolationType(Enum):
7
- INVALID_JSON_FORMAT = 0
8
- MISSING_OBLIGATORY_FIELD = 1
9
- INVALID_VALUE_IN_CATEGORY = 2
10
- NULL_VALUE = 3
11
-
12
- def get_error_message(self):
13
- error_message_dict = {
14
- 0: "invalid json format",
15
- 1: "missing obligatory field",
16
- 2: "invalid value in category",
17
- 3: "attribute has null value",
18
- }
19
- return error_message_dict[self.value]
20
-
21
-
22
- class DataViolation:
23
- def __init__(self, violation_type: ViolationType):
24
- self.violation_type = violation_type
25
-
26
-
27
- class FormatViolation(DataViolation):
28
- def __init__(self, decoding_error):
29
- super().__init__(ViolationType.INVALID_JSON_FORMAT)
30
- self.decoding_error = decoding_error
31
-
32
-
33
- class FieldViolation(DataViolation):
34
- def __init__(self, violation_type: ViolationType, datapoint, violating_field):
35
- super().__init__(violation_type)
36
- self.datapoint = datapoint
37
- self.violating_field = violating_field
38
-
39
-
40
- def get_missing_obligatory_fields_violations(p, obligatory_fields):
41
- violations = []
42
- for field in obligatory_fields:
43
- if field not in p.keys():
44
- violations.append(FieldViolation(ViolationType.MISSING_OBLIGATORY_FIELD, p, field))
45
- return violations
46
-
47
-
48
- def get_invalid_value_in_category_violations(p, categories):
49
- violations = []
50
- for category in categories & p.keys():
51
- category_value_in_point = p[category]
52
- valid_values_set = categories[category]
53
- if isinstance(category_value_in_point, list):
54
- for attribute_value in category_value_in_point:
55
- if attribute_value not in valid_values_set:
56
- violations.append(
57
- FieldViolation(ViolationType.INVALID_VALUE_IN_CATEGORY, p, category)
58
- )
59
- else:
60
- if category_value_in_point not in valid_values_set:
61
- violations.append(
62
- FieldViolation(ViolationType.INVALID_VALUE_IN_CATEGORY, p, category)
63
- )
64
- return violations
65
-
66
-
67
- def get_null_values_violations(p):
68
- violations = []
69
- for attribute, value in p.items():
70
- if value is None:
71
- violations.append(FieldViolation(ViolationType.NULL_VALUE, p, attribute))
72
- return violations
73
-
74
-
75
- def report_data_violations_from_json(json_database):
76
- map_data = json_database["map"]
77
- datapoints = map_data["data"]
78
- categories = map_data["categories"]
79
- obligatory_fields = map_data["location_obligatory_fields"]
80
-
81
- data_violations = []
82
-
83
- for p in datapoints:
84
- data_violations += get_missing_obligatory_fields_violations(p, obligatory_fields)
85
- data_violations += get_invalid_value_in_category_violations(p, categories)
86
- data_violations += get_null_values_violations(p)
87
-
88
- return data_violations
89
-
90
-
91
- def report_data_violations_from_json_file(path_to_json_file):
92
- with open(path_to_json_file) as json_file:
93
- try:
94
- return report_data_violations_from_json(json.load(json_file))
95
- except json.JSONDecodeError as e:
96
- return [FormatViolation(e)]
97
-
98
-
99
- def print_reported_violations(data_violations): # pragma: no cover
100
- for violation in data_violations:
101
- violation_type = violation.violation_type
102
- if violation_type == ViolationType.INVALID_JSON_FORMAT:
103
- print("DATA ERROR: invalid json format", file=stderr)
104
- print(violation.decoding_error, file=stderr)
105
- else:
106
- violating_field = violation.violating_field
107
- violation_type_error = violation_type.get_error_message()
108
- print(
109
- f"DATA ERROR: {violation_type_error} {violating_field} in datapoint:", file=stderr
110
- )
111
- print(violation.datapoint, file=stderr)
112
-
113
-
114
- if __name__ == "__main__": # pragma: no cover
115
- data_violations = report_data_violations_from_json_file(argv[1])
116
- if data_violations == []:
117
- print("All data is valid", file=stderr)
118
- else:
119
- print_reported_violations(data_violations)