goodmap 0.4.4__tar.gz → 0.5.1__tar.gz
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-0.4.4 → goodmap-0.5.1}/PKG-INFO +11 -3
- {goodmap-0.4.4 → goodmap-0.5.1}/README.md +9 -1
- goodmap-0.5.1/goodmap/core_api.py +352 -0
- {goodmap-0.4.4 → goodmap-0.5.1}/goodmap/data_models/location.py +2 -1
- {goodmap-0.4.4 → goodmap-0.5.1}/goodmap/data_validator.py +1 -1
- goodmap-0.5.1/goodmap/db.py +564 -0
- {goodmap-0.4.4 → goodmap-0.5.1}/goodmap/goodmap.py +27 -1
- goodmap-0.5.1/goodmap/templates/goodmap-admin.html +743 -0
- {goodmap-0.4.4 → goodmap-0.5.1}/pyproject.toml +9 -5
- goodmap-0.4.4/goodmap/core_api.py +0 -165
- goodmap-0.4.4/goodmap/db.py +0 -116
- goodmap-0.4.4/goodmap/templates/admin.html +0 -0
- {goodmap-0.4.4 → goodmap-0.5.1}/LICENSE.md +0 -0
- {goodmap-0.4.4 → goodmap-0.5.1}/goodmap/__init__.py +0 -0
- {goodmap-0.4.4 → goodmap-0.5.1}/goodmap/core.py +0 -0
- {goodmap-0.4.4 → goodmap-0.5.1}/goodmap/formatter.py +0 -0
- {goodmap-0.4.4 → goodmap-0.5.1}/goodmap/templates/map.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: goodmap
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: Map engine to serve all the people :)
|
|
5
5
|
Author: Krzysztof Kolodzinski
|
|
6
6
|
Author-email: krzysztof.kolodzinski@problematy.pl
|
|
@@ -22,7 +22,7 @@ Requires-Dist: google-cloud-storage (>=2.7.0,<3.0.0)
|
|
|
22
22
|
Requires-Dist: gql (>=3.4.0,<4.0.0)
|
|
23
23
|
Requires-Dist: gunicorn (>=20.1.0,<21.0.0)
|
|
24
24
|
Requires-Dist: humanize (>=4.6.0,<5.0.0)
|
|
25
|
-
Requires-Dist: platzky (>=0.
|
|
25
|
+
Requires-Dist: platzky (>=0.3.6,<0.4.0)
|
|
26
26
|
Requires-Dist: pydantic (>=2.7.1,<3.0.0)
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
|
|
@@ -82,11 +82,17 @@ you can simply run app with test dataset provided in `tests/e2e_tests` directory
|
|
|
82
82
|
### Configuration
|
|
83
83
|
|
|
84
84
|
If you want to serve app with your configuration rename config-template.yml to config.yml and change its contents according to your needs.
|
|
85
|
-
Values descriptions you can find inside config-template.yml.
|
|
86
85
|
|
|
87
86
|
Afterwards run it with:
|
|
88
87
|
> poetry run flask --app 'goodmap.goodmap:create_app(config_path="/PATH/TO/YOUR/CONFIG")' --debug run
|
|
89
88
|
|
|
89
|
+
|
|
90
|
+
| Option | Description |
|
|
91
|
+
|--------------------------|------------------------------------------------------------------------------------------------------------------------------------|
|
|
92
|
+
| USE_LAZY_LOADING | Loads point data only after the user clicks a point. If set to false, point data is loaded together with the initial map. |
|
|
93
|
+
| FAKE_LOGIN | If set to true, allows access to the admin panel by simply selecting the role instead of logging in. **DO NOT USE IN PRODUCTION!** |
|
|
94
|
+
| SHOW_ACCESSIBILITY_TABLE | If set as true it shows special view to help with accessing application. |
|
|
95
|
+
|
|
90
96
|
## Database
|
|
91
97
|
|
|
92
98
|
The database is stored in JSON, in the `map` section. For an example database see `tests/e2e_tests/e2e_test_data.json`. The first subsection `data` consists of the actual datapoints, representing points on a map.
|
|
@@ -121,3 +127,5 @@ You can find examples of working configuration and database in `tests/e2e_tests`
|
|
|
121
127
|
- `e2e_test_config.yml`
|
|
122
128
|
- `e2e_test_data.json`
|
|
123
129
|
|
|
130
|
+
|
|
131
|
+
|
|
@@ -54,11 +54,17 @@ you can simply run app with test dataset provided in `tests/e2e_tests` directory
|
|
|
54
54
|
### Configuration
|
|
55
55
|
|
|
56
56
|
If you want to serve app with your configuration rename config-template.yml to config.yml and change its contents according to your needs.
|
|
57
|
-
Values descriptions you can find inside config-template.yml.
|
|
58
57
|
|
|
59
58
|
Afterwards run it with:
|
|
60
59
|
> poetry run flask --app 'goodmap.goodmap:create_app(config_path="/PATH/TO/YOUR/CONFIG")' --debug run
|
|
61
60
|
|
|
61
|
+
|
|
62
|
+
| Option | Description |
|
|
63
|
+
|--------------------------|------------------------------------------------------------------------------------------------------------------------------------|
|
|
64
|
+
| USE_LAZY_LOADING | Loads point data only after the user clicks a point. If set to false, point data is loaded together with the initial map. |
|
|
65
|
+
| FAKE_LOGIN | If set to true, allows access to the admin panel by simply selecting the role instead of logging in. **DO NOT USE IN PRODUCTION!** |
|
|
66
|
+
| SHOW_ACCESSIBILITY_TABLE | If set as true it shows special view to help with accessing application. |
|
|
67
|
+
|
|
62
68
|
## Database
|
|
63
69
|
|
|
64
70
|
The database is stored in JSON, in the `map` section. For an example database see `tests/e2e_tests/e2e_test_data.json`. The first subsection `data` consists of the actual datapoints, representing points on a map.
|
|
@@ -92,3 +98,5 @@ You can define the fields in all these subsections. Besides these types of field
|
|
|
92
98
|
You can find examples of working configuration and database in `tests/e2e_tests` named:
|
|
93
99
|
- `e2e_test_config.yml`
|
|
94
100
|
- `e2e_test_data.json`
|
|
101
|
+
|
|
102
|
+
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import importlib.metadata
|
|
2
|
+
import uuid
|
|
3
|
+
|
|
4
|
+
from flask import Blueprint, jsonify, make_response, request
|
|
5
|
+
from flask_babel import gettext
|
|
6
|
+
from flask_restx import Api, Resource, fields
|
|
7
|
+
from platzky.config import LanguagesMapping
|
|
8
|
+
|
|
9
|
+
from goodmap.formatter import prepare_pin
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def make_tuple_translation(keys_to_translate):
|
|
13
|
+
return [(x, gettext(x)) for x in keys_to_translate]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def paginate_results(items, raw_params, sort_by_default=None):
|
|
17
|
+
"""
|
|
18
|
+
Apply pagination and sorting to a list of items.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
items: The list of items to paginate
|
|
22
|
+
raw_params: The query parameters dictionary
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Tuple of (paginated_items, pagination_metadata)
|
|
26
|
+
"""
|
|
27
|
+
# Extract pagination parameters
|
|
28
|
+
try:
|
|
29
|
+
page = int(raw_params.pop("page", ["1"])[0])
|
|
30
|
+
except ValueError:
|
|
31
|
+
page = 1
|
|
32
|
+
|
|
33
|
+
per_page_raw = raw_params.pop("per_page", [None])[0]
|
|
34
|
+
if per_page_raw is None:
|
|
35
|
+
per_page = 20
|
|
36
|
+
elif per_page_raw == "all":
|
|
37
|
+
per_page = None
|
|
38
|
+
else:
|
|
39
|
+
try:
|
|
40
|
+
per_page = max(1, int(per_page_raw))
|
|
41
|
+
except ValueError:
|
|
42
|
+
per_page = 20
|
|
43
|
+
|
|
44
|
+
sort_by = raw_params.pop("sort_by", [None])[0] or sort_by_default
|
|
45
|
+
sort_order = raw_params.pop("sort_order", ["asc"])[0].lower()
|
|
46
|
+
|
|
47
|
+
def get_sort_key(item):
|
|
48
|
+
if not sort_by:
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
value = None
|
|
52
|
+
if isinstance(item, dict):
|
|
53
|
+
value = item.get(sort_by)
|
|
54
|
+
else:
|
|
55
|
+
value = getattr(item, sort_by, None)
|
|
56
|
+
|
|
57
|
+
return (value is not None, value)
|
|
58
|
+
|
|
59
|
+
if sort_by:
|
|
60
|
+
reverse = sort_order == "desc"
|
|
61
|
+
items.sort(key=get_sort_key, reverse=reverse)
|
|
62
|
+
|
|
63
|
+
# Apply pagination
|
|
64
|
+
total = len(items)
|
|
65
|
+
if per_page:
|
|
66
|
+
start = (page - 1) * per_page
|
|
67
|
+
end = start + per_page
|
|
68
|
+
page_items = items[start:end]
|
|
69
|
+
total_pages = (total + per_page - 1) // per_page
|
|
70
|
+
else:
|
|
71
|
+
page_items = items
|
|
72
|
+
total_pages = 1
|
|
73
|
+
page = 1
|
|
74
|
+
per_page = total
|
|
75
|
+
|
|
76
|
+
return page_items, {
|
|
77
|
+
"total": total,
|
|
78
|
+
"page": page,
|
|
79
|
+
"per_page": per_page,
|
|
80
|
+
"total_pages": total_pages,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def core_pages(
|
|
85
|
+
database, languages: LanguagesMapping, notifier_function, csrf_generator, location_model
|
|
86
|
+
) -> Blueprint:
|
|
87
|
+
core_api_blueprint = Blueprint("api", __name__, url_prefix="/api")
|
|
88
|
+
core_api = Api(core_api_blueprint, doc="/doc", version="0.1")
|
|
89
|
+
|
|
90
|
+
location_report_model = core_api.model(
|
|
91
|
+
"LocationReport",
|
|
92
|
+
{
|
|
93
|
+
"id": fields.String(required=True, description="Location ID"),
|
|
94
|
+
"description": fields.String(required=True, description="Description of the problem"),
|
|
95
|
+
},
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# TODO get this from Location pydantic model
|
|
99
|
+
suggested_location_model = core_api.model(
|
|
100
|
+
"LocationSuggestion",
|
|
101
|
+
{
|
|
102
|
+
"name": fields.String(required=False, description="Organization name"),
|
|
103
|
+
"position": fields.String(required=True, description="Location of the suggestion"),
|
|
104
|
+
"photo": fields.String(required=False, description="Photo of the location"),
|
|
105
|
+
},
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
@core_api.route("/suggest-new-point")
|
|
109
|
+
class NewLocation(Resource):
|
|
110
|
+
@core_api.expect(suggested_location_model)
|
|
111
|
+
def post(self):
|
|
112
|
+
"""Suggest new location"""
|
|
113
|
+
try:
|
|
114
|
+
suggested_location = request.get_json()
|
|
115
|
+
suggested_location.update({"uuid": str(uuid.uuid4())})
|
|
116
|
+
location = location_model.model_validate(suggested_location)
|
|
117
|
+
database.add_suggestion(location.model_dump())
|
|
118
|
+
message = (
|
|
119
|
+
f"A new location has been suggested under uuid: '{location.uuid}' "
|
|
120
|
+
f"at position: {location.position}"
|
|
121
|
+
)
|
|
122
|
+
notifier_function(message)
|
|
123
|
+
except ValueError as e:
|
|
124
|
+
return make_response(jsonify({"message": f"Invalid location data: {e}"}), 400)
|
|
125
|
+
except Exception as e:
|
|
126
|
+
return make_response(jsonify({"message": f"Error sending notification : {e}"}), 400)
|
|
127
|
+
return make_response(jsonify({"message": "Location suggested"}), 200)
|
|
128
|
+
|
|
129
|
+
@core_api.route("/report-location")
|
|
130
|
+
class ReportLocation(Resource):
|
|
131
|
+
@core_api.expect(location_report_model)
|
|
132
|
+
def post(self):
|
|
133
|
+
"""Report location"""
|
|
134
|
+
try:
|
|
135
|
+
location_report = request.get_json()
|
|
136
|
+
report = {
|
|
137
|
+
"uuid": str(uuid.uuid4()),
|
|
138
|
+
"location_id": location_report["id"],
|
|
139
|
+
"description": location_report["description"],
|
|
140
|
+
"status": "pending",
|
|
141
|
+
"priority": "medium",
|
|
142
|
+
}
|
|
143
|
+
database.add_report(report)
|
|
144
|
+
message = (
|
|
145
|
+
f"A location has been reported: '{location_report['id']}' "
|
|
146
|
+
f"with problem: {location_report['description']}"
|
|
147
|
+
)
|
|
148
|
+
notifier_function(message)
|
|
149
|
+
except KeyError as e:
|
|
150
|
+
error_message = gettext("Error reporting location")
|
|
151
|
+
return make_response(jsonify({"message": f"{error_message} : {e}"}), 400)
|
|
152
|
+
except Exception as e:
|
|
153
|
+
error_message = gettext("Error sending notification")
|
|
154
|
+
return make_response(jsonify({"message": f"{error_message} : {e}"}), 400)
|
|
155
|
+
return make_response(jsonify({"message": gettext("Location reported")}), 200)
|
|
156
|
+
|
|
157
|
+
@core_api.route("/locations")
|
|
158
|
+
class GetLocations(Resource):
|
|
159
|
+
def get(self):
|
|
160
|
+
"""
|
|
161
|
+
Shows list of locations with uuid and position
|
|
162
|
+
"""
|
|
163
|
+
query_params = request.args.to_dict(flat=False)
|
|
164
|
+
all_locations = database.get_locations(query_params)
|
|
165
|
+
return jsonify([x.basic_info() for x in all_locations])
|
|
166
|
+
|
|
167
|
+
@core_api.route("/location/<location_id>")
|
|
168
|
+
class GetLocation(Resource):
|
|
169
|
+
def get(self, location_id):
|
|
170
|
+
"""
|
|
171
|
+
Shows a single location with all data
|
|
172
|
+
"""
|
|
173
|
+
location = database.get_location(location_id)
|
|
174
|
+
|
|
175
|
+
# TODO getting visible_data and meta_data should be taken from db methods
|
|
176
|
+
# e.g. db.get_visible_data() and db.get_meta_data()
|
|
177
|
+
# visible_data and meta_data should be models
|
|
178
|
+
all_data = database.get_data()
|
|
179
|
+
visible_data = all_data["visible_data"]
|
|
180
|
+
meta_data = all_data["meta_data"]
|
|
181
|
+
|
|
182
|
+
formatted_data = prepare_pin(location.model_dump(), visible_data, meta_data)
|
|
183
|
+
return jsonify(formatted_data)
|
|
184
|
+
|
|
185
|
+
@core_api.route("/version")
|
|
186
|
+
class Version(Resource):
|
|
187
|
+
def get(self):
|
|
188
|
+
"""Shows backend version"""
|
|
189
|
+
version_info = {"backend": importlib.metadata.version("goodmap")}
|
|
190
|
+
return jsonify(version_info)
|
|
191
|
+
|
|
192
|
+
@core_api.route("/categories")
|
|
193
|
+
class Categories(Resource):
|
|
194
|
+
def get(self):
|
|
195
|
+
"""Shows all available categories"""
|
|
196
|
+
all_data = database.get_data()
|
|
197
|
+
categories = make_tuple_translation(all_data["categories"].keys())
|
|
198
|
+
return jsonify(categories)
|
|
199
|
+
|
|
200
|
+
@core_api.route("/languages")
|
|
201
|
+
class Languages(Resource):
|
|
202
|
+
def get(self):
|
|
203
|
+
"""Shows all available languages"""
|
|
204
|
+
return jsonify(languages)
|
|
205
|
+
|
|
206
|
+
@core_api.route("/category/<category_type>")
|
|
207
|
+
class CategoryTypes(Resource):
|
|
208
|
+
def get(self, category_type):
|
|
209
|
+
"""Shows all available types in category"""
|
|
210
|
+
all_data = database.get_data()
|
|
211
|
+
local_data = make_tuple_translation(all_data["categories"][category_type])
|
|
212
|
+
return jsonify(local_data)
|
|
213
|
+
|
|
214
|
+
@core_api.route("/generate-csrf-token")
|
|
215
|
+
class CsrfToken(Resource):
|
|
216
|
+
def get(self):
|
|
217
|
+
csrf_token = csrf_generator()
|
|
218
|
+
return {"csrf_token": csrf_token}
|
|
219
|
+
|
|
220
|
+
@core_api.route("/admin/locations")
|
|
221
|
+
class AdminManageLocations(Resource):
|
|
222
|
+
def get(self):
|
|
223
|
+
"""
|
|
224
|
+
Shows full list of locations, with optional server-side pagination, sorting,
|
|
225
|
+
and filtering.
|
|
226
|
+
"""
|
|
227
|
+
# Raw query params from request
|
|
228
|
+
raw_params = request.args.to_dict(flat=False)
|
|
229
|
+
all_locations = database.get_locations(raw_params)
|
|
230
|
+
page_items, pagination = paginate_results(
|
|
231
|
+
all_locations, raw_params, sort_by_default="name"
|
|
232
|
+
)
|
|
233
|
+
items = [x.model_dump() for x in page_items]
|
|
234
|
+
return jsonify({"items": items, **pagination})
|
|
235
|
+
|
|
236
|
+
def post(self):
|
|
237
|
+
"""
|
|
238
|
+
Creates a new location
|
|
239
|
+
"""
|
|
240
|
+
location_data = request.get_json()
|
|
241
|
+
try:
|
|
242
|
+
location_data.update({"uuid": str(uuid.uuid4())})
|
|
243
|
+
location = location_model.model_validate(location_data)
|
|
244
|
+
database.add_location(location.model_dump())
|
|
245
|
+
except ValueError as e:
|
|
246
|
+
return make_response(jsonify({"message": f"Invalid location data: {e}"}), 400)
|
|
247
|
+
except Exception as e:
|
|
248
|
+
return make_response(jsonify({"message": f"Error creating location: {e}"}), 400)
|
|
249
|
+
return jsonify(location.model_dump())
|
|
250
|
+
|
|
251
|
+
@core_api.route("/admin/locations/<location_id>")
|
|
252
|
+
class AdminManageLocation(Resource):
|
|
253
|
+
def put(self, location_id):
|
|
254
|
+
"""
|
|
255
|
+
Updates a single location
|
|
256
|
+
"""
|
|
257
|
+
location_data = request.get_json()
|
|
258
|
+
try:
|
|
259
|
+
location_data.update({"uuid": location_id})
|
|
260
|
+
location = location_model.model_validate(location_data)
|
|
261
|
+
database.update_location(location_id, location.model_dump())
|
|
262
|
+
except ValueError as e:
|
|
263
|
+
return make_response(jsonify({"message": f"Invalid location data: {e}"}), 400)
|
|
264
|
+
except Exception as e:
|
|
265
|
+
return make_response(jsonify({"message": f"Error updating location: {e}"}), 400)
|
|
266
|
+
return jsonify(location.model_dump())
|
|
267
|
+
|
|
268
|
+
def delete(self, location_id):
|
|
269
|
+
"""
|
|
270
|
+
Deletes a single location
|
|
271
|
+
"""
|
|
272
|
+
try:
|
|
273
|
+
database.delete_location(location_id)
|
|
274
|
+
except ValueError as e:
|
|
275
|
+
return make_response(jsonify({"message": f"Location not found: {e}"}), 404)
|
|
276
|
+
except Exception as e:
|
|
277
|
+
return make_response(jsonify({"message": f"Error deleting location: {e}"}), 400)
|
|
278
|
+
return "", 204
|
|
279
|
+
|
|
280
|
+
@core_api.route("/admin/suggestions")
|
|
281
|
+
class AdminManageSuggestions(Resource):
|
|
282
|
+
def get(self):
|
|
283
|
+
"""
|
|
284
|
+
List location suggestions, with optional server-side pagination, sorting,
|
|
285
|
+
and filtering by status.
|
|
286
|
+
"""
|
|
287
|
+
raw_params = request.args.to_dict(flat=False)
|
|
288
|
+
suggestions = database.get_suggestions(raw_params)
|
|
289
|
+
page_items, pagination = paginate_results(suggestions, raw_params)
|
|
290
|
+
return jsonify({"items": page_items, **pagination})
|
|
291
|
+
|
|
292
|
+
@core_api.route("/admin/suggestions/<suggestion_id>")
|
|
293
|
+
class AdminManageSuggestion(Resource):
|
|
294
|
+
def put(self, suggestion_id):
|
|
295
|
+
"""
|
|
296
|
+
Accept or reject a location suggestion
|
|
297
|
+
"""
|
|
298
|
+
try:
|
|
299
|
+
data = request.get_json()
|
|
300
|
+
status = data.get("status")
|
|
301
|
+
if status not in ("accepted", "rejected"):
|
|
302
|
+
return make_response(jsonify({"message": f"Invalid status: {status}"}), 400)
|
|
303
|
+
suggestion = database.get_suggestion(suggestion_id)
|
|
304
|
+
if not suggestion:
|
|
305
|
+
return make_response(jsonify({"message": "Suggestion not found"}), 404)
|
|
306
|
+
if suggestion.get("status") != "pending":
|
|
307
|
+
return make_response(jsonify({"message": "Suggestion already processed"}), 400)
|
|
308
|
+
if status == "accepted":
|
|
309
|
+
suggestion_data = {k: v for k, v in suggestion.items() if k != "status"}
|
|
310
|
+
database.add_location(suggestion_data)
|
|
311
|
+
database.update_suggestion(suggestion_id, status)
|
|
312
|
+
except ValueError as e:
|
|
313
|
+
return make_response(jsonify({"message": f"{e}"}), 400)
|
|
314
|
+
return jsonify(database.get_suggestion(suggestion_id))
|
|
315
|
+
|
|
316
|
+
@core_api.route("/admin/reports")
|
|
317
|
+
class AdminManageReports(Resource):
|
|
318
|
+
def get(self):
|
|
319
|
+
"""
|
|
320
|
+
List location reports, with optional server-side pagination, sorting,
|
|
321
|
+
and filtering by status/priority.
|
|
322
|
+
"""
|
|
323
|
+
raw_params = request.args.to_dict(flat=False)
|
|
324
|
+
reports = database.get_reports(raw_params)
|
|
325
|
+
page_items, pagination = paginate_results(reports, raw_params)
|
|
326
|
+
return jsonify({"items": page_items, **pagination})
|
|
327
|
+
|
|
328
|
+
@core_api.route("/admin/reports/<report_id>")
|
|
329
|
+
class AdminManageReport(Resource):
|
|
330
|
+
def put(self, report_id):
|
|
331
|
+
"""
|
|
332
|
+
Update a report's status and/or priority
|
|
333
|
+
"""
|
|
334
|
+
try:
|
|
335
|
+
data = request.get_json()
|
|
336
|
+
status = data.get("status")
|
|
337
|
+
priority = data.get("priority")
|
|
338
|
+
valid_status = ("resolved", "rejected")
|
|
339
|
+
valid_priority = ("critical", "high", "medium", "low")
|
|
340
|
+
if status and status not in valid_status:
|
|
341
|
+
return make_response(jsonify({"message": f"Invalid status: {status}"}), 400)
|
|
342
|
+
if priority and priority not in valid_priority:
|
|
343
|
+
return make_response(jsonify({"message": f"Invalid priority: {priority}"}), 400)
|
|
344
|
+
report = database.get_report(report_id)
|
|
345
|
+
if not report:
|
|
346
|
+
return make_response(jsonify({"message": "Report not found"}), 404)
|
|
347
|
+
database.update_report(report_id, status=status, priority=priority)
|
|
348
|
+
except ValueError as e:
|
|
349
|
+
return make_response(jsonify({"message": f"{e}"}), 400)
|
|
350
|
+
return jsonify(database.get_report(report_id))
|
|
351
|
+
|
|
352
|
+
return core_api_blueprint
|
|
@@ -6,6 +6,7 @@ from pydantic import BaseModel, Field, create_model, field_validator
|
|
|
6
6
|
class LocationBase(BaseModel, extra="allow"):
|
|
7
7
|
position: tuple[float, float]
|
|
8
8
|
uuid: str
|
|
9
|
+
remark: bool = False
|
|
9
10
|
|
|
10
11
|
@field_validator("position")
|
|
11
12
|
@classmethod
|
|
@@ -17,7 +18,7 @@ class LocationBase(BaseModel, extra="allow"):
|
|
|
17
18
|
return v
|
|
18
19
|
|
|
19
20
|
def basic_info(self):
|
|
20
|
-
return {"uuid": self.uuid, "position": self.position}
|
|
21
|
+
return {"uuid": self.uuid, "position": self.position, "remark": bool(self.remark)}
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
def create_location_model(obligatory_fields: list[tuple[str, Type[Any]]]) -> Type[BaseModel]:
|
|
@@ -50,7 +50,7 @@ def get_invalid_value_in_category_violations(p, categories):
|
|
|
50
50
|
for category in categories & p.keys():
|
|
51
51
|
category_value_in_point = p[category]
|
|
52
52
|
valid_values_set = categories[category]
|
|
53
|
-
if
|
|
53
|
+
if isinstance(category_value_in_point, list):
|
|
54
54
|
for attribute_value in category_value_in_point:
|
|
55
55
|
if attribute_value not in valid_values_set:
|
|
56
56
|
violations.append(
|