From f7cace78a916fc66b20481840e0b0e81b6fbde24 Mon Sep 17 00:00:00 2001 From: Christian Winger Date: Thu, 18 Dec 2025 10:16:01 +0100 Subject: [PATCH 1/7] removed unused API views: TableFieldsAPIView, AdvancedInfoAPIView --- api/actions.py | 50 ++++++-------------------------------------------- api/urls.py | 9 +-------- api/views.py | 34 +++------------------------------- 3 files changed, 10 insertions(+), 83 deletions(-) diff --git a/api/actions.py b/api/actions.py index a8dffa195..597972fd4 100644 --- a/api/actions.py +++ b/api/actions.py @@ -1,5 +1,6 @@ -"""Api actions. +"""Api actions.""" +__license__ = """ SPDX-FileCopyrightText: 2025 Pierre Francois © Reiner Lemoine Institut SPDX-FileCopyrightText: 2025 Christian Winger © Öko-Institut e.V. SPDX-FileCopyrightText: 2025 Eike Broda @@ -744,7 +745,6 @@ def get_column(d): def column_alter(query, table_obj: Table, column): - if column == "id": raise APIError("You cannot alter the id column") alter_preamble = 'ALTER TABLE "{schema}"."{table}" ALTER COLUMN "{column}" '.format( @@ -979,7 +979,6 @@ def __internal_select(query, context): def __change_rows( table_obj: Table, request, context, target_sa_table: "SATable", setter, fields=None ) -> dict: - query: dict = { "from": { "type": "table", @@ -1254,7 +1253,7 @@ def move_publish(table_obj: Table, topic, embargo_period): if not license_check: raise APIError( - "A issue with the license from the metadata was found: " f"{license_error}" + f"A issue with the license from the metadata was found: {license_error}" ) if embargo_period in ["6_months", "1_year"]: @@ -1319,29 +1318,6 @@ def fetchmany(request: dict, context): return cursor.fetchmany(int(request["size"])) -def getValue(table_obj: Table, column, id): - sql = 'SELECT {column} FROM "{schema}"."{table}" WHERE id={id}'.format( - column=column, schema=table_obj.oedb_schema, table=table_obj.name, id=id - ) - - session = _create_oedb_session() - - try: - result = session_execute(session, sql) - - returnValue = None - for row in result: - returnValue = row[column] - - return returnValue - except Exception as e: - logger.error("SQL Action failed. \n Error:\n" + str(e)) - session.rollback() - finally: - session.close() - return None - - def apply_changes(table_obj: Table, cursor: AbstractCursor | None = None): """Apply changes from the meta tables to the actual table. @@ -1366,7 +1342,6 @@ def add_type(d, type): cursor = cast(AbstractCursor, connection.cursor()) # type:ignore TODO try: - columns = list(describe_columns(table_obj).keys()) extended_columns = columns + ["_submitted", "_id"] @@ -1377,9 +1352,7 @@ def add_type(d, type): insert_sa_table = oedb_table._insert_table.get_sa_table() cursor_execute( cursor, - "select * " - 'from "{schema}"."{table}" ' - "where _applied = FALSE;".format( + 'select * from "{schema}"."{table}" where _applied = FALSE;'.format( schema=insert_sa_table.schema, table=insert_sa_table.name ), ) @@ -1398,9 +1371,7 @@ def add_type(d, type): update_sa_table = oedb_table._edit_table.get_sa_table() cursor_execute( cursor, - "select * " - 'from "{schema}"."{table}" ' - "where _applied = FALSE;".format( + 'select * from "{schema}"."{table}" where _applied = FALSE;'.format( schema=update_sa_table.schema, table=update_sa_table.name ), ) @@ -1419,9 +1390,7 @@ def add_type(d, type): delete_sa_table = oedb_table._delete_table.get_sa_table() cursor_execute( cursor, - "select * " - 'from "{schema}"."{table}" ' - "where _applied = FALSE;".format( + 'select * from "{schema}"."{table}" where _applied = FALSE;'.format( schema=delete_sa_table.schema, table=delete_sa_table.name ), ) @@ -1478,7 +1447,6 @@ def _apply_stack(cursor: AbstractCursor, sa_table: "SATable", changes, change_ty def set_applied( session: AbstractCursor | Session, sa_table: "SATable", rids, mode: int ): - # TODO:permission check is still done outside of this function, # so we pass user=None oedb_table = Table.objects.get(name=sa_table.name).get_oedb_table_proxy(user=None) @@ -1763,11 +1731,6 @@ def get_table_names( return [t.name for t in Table.objects.all()] -def data_info(request: dict, context: dict | None = None) -> dict: - # TODO: can we remove this endpoint? - return request - - def has_schema(request: dict, context: dict | None = None) -> bool: # TODO can we remove this endpoint return request.get("schema") in get_schema_names() @@ -1798,7 +1761,6 @@ def data_search(request: dict, context: dict | None = None) -> dict: def data_insert(request: dict, context: dict) -> dict: - cursor = load_cursor_from_context(context) # If the insert request is not for a meta table, change the request to do so table_obj = table_or_404_from_dict(request) diff --git a/api/urls.py b/api/urls.py index ed2dd9eaf..297329a64 100644 --- a/api/urls.py +++ b/api/urls.py @@ -1,4 +1,4 @@ -""" +__license__ = """ SPDX-FileCopyrightText: 2025 Adel Memariani © Otto-von-Guericke-Universität Magdeburg SPDX-FileCopyrightText: 2025 Christian Winger © Öko-Institut e.V. SPDX-FileCopyrightText: 2025 Johann Wagner © Otto-von-Guericke-Universität Magdeburg @@ -67,7 +67,6 @@ usrprop_api_view, ) -# from api.views import TableFieldsAPIView, AdvancedInfoAPIView app_name = "api" pgsql_qualifier = r"[\w\d_]+" @@ -102,11 +101,6 @@ TableColumnAPIView.as_view(), name="table-columns", ), - # re_path( - # r"^(?P[\w\d_\s]+)/id/(?P[\d]+)/column/(?P[\w\d_\s]+)/$", # noqa - # TableFieldsAPIView.as_view(), - # name="table-fields", - # ), re_path( r"^(?P
[\w\d_\s]+)/rows/(?P[\d]+)?$", # noqa TableRowsAPIView.as_view(), @@ -135,7 +129,6 @@ ), re_path(r"^delete", AdvancedDeleteAPIView, name="advanced-delete"), re_path(r"^update", AdvancedUpdateAPIView, name="advanced-update"), - # re_path(r"^info", AdvancedInfoAPIView, name="advanced-info"), re_path(r"^has_schema", AdvancedHasSchemaAPIView, name="advanced-has-schema"), re_path(r"^has_table", AdvancedHasTableAPIView, name="advanced-has-table"), re_path( diff --git a/api/views.py b/api/views.py index 6f52b31f1..9074747c2 100644 --- a/api/views.py +++ b/api/views.py @@ -40,7 +40,7 @@ import json import re -import geoalchemy2 # noqa: Although this import seems unused is has to be here +import geoalchemy2 # noqa:F401 Although this import seems unused is has to be here import requests import zipstream from django.contrib.auth.mixins import LoginRequiredMixin @@ -68,7 +68,6 @@ column_alter, commit_raw_connection, data_delete, - data_info, data_insert, data_search, data_update, @@ -98,7 +97,6 @@ get_unique_constraints, get_view_definition, get_view_names, - getValue, has_schema, has_table, list_table_sizes, @@ -200,7 +198,6 @@ def post(self, request: Request, table: str) -> JsonLikeResponse: metadata, error = try_validate_metadata(metadata) if metadata is not None: - # update/sync keywords with tags before saving metadata # TODO make this iter over all resources keywords = metadata["resources"][0].get("keywords", []) or [] @@ -371,7 +368,6 @@ def put(self, request: Request, table: str) -> JsonLikeResponse: metadata = payload_query.get("metadata") if metadata: - set_table_metadata(table=table, metadata=metadata) return JsonResponse({}, status=status.HTTP_201_CREATED) @@ -397,7 +393,7 @@ def get( try: response = response[column] except KeyError: - raise APIError("The column specified is not part of " "this table.") + raise APIError("The column specified is not part of this table.") return JsonResponse(response) @api_exception @@ -418,29 +414,6 @@ def put(self, request: Request, table: str, column: str) -> JsonLikeResponse: return JsonResponse({}, status=201) -class TableFieldsAPIView(APIView): - # TODO: is this really used? - @api_exception - @method_decorator(never_cache) - def get( - self, - request: Request, - table: str, - row_id: int, - column: str | None = None, - ) -> JsonLikeResponse: - table_obj = table_or_404(table=table) - - if not is_pg_qual(table) or not is_pg_qual(row_id) or not is_pg_qual(column): - return ModJsonResponse({"error": "Bad Request", "http_status": 400}) - - returnValue = getValue(table_obj, column, row_id) - if returnValue is None: - return JsonResponse({}, status=404) - else: - return JsonResponse(returnValue, status=200) - - class TableMovePublishAPIView(APIView): @api_exception @require_admin_permission @@ -795,7 +768,7 @@ def __insert_row( ): if row_id and row.get("id", int(row_id)) != int(row_id): return response_error( - "The id given in the query does not " "match the id given in the url" + "The id given in the query does not match the id given in the url" ) if row_id: row["id"] = row_id @@ -1183,7 +1156,6 @@ def get(self, request: Request) -> JsonLikeResponse: AdvancedDeleteAPIView = create_ajax_handler(data_delete, requires_cursor=True) AdvancedUpdateAPIView = create_ajax_handler(data_update, requires_cursor=True) -AdvancedInfoAPIView = create_ajax_handler(data_info) AdvancedHasSchemaAPIView = create_ajax_handler(has_schema) AdvancedHasTableAPIView = create_ajax_handler(has_table) AdvancedGetSchemaNamesAPIView = create_ajax_handler(get_schema_names) From f0294328201f1ba60504a909ea2d8bd250f97d8e Mon Sep 17 00:00:00 2001 From: Christian Winger Date: Thu, 18 Dec 2025 10:16:47 +0100 Subject: [PATCH 2/7] AdvancedFetchAPIView: use fetchtype="many" instead of "all" --- api/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/urls.py b/api/urls.py index 297329a64..3d3a74d11 100644 --- a/api/urls.py +++ b/api/urls.py @@ -242,7 +242,7 @@ re_path( r"^cursor/fetch_many", AdvancedFetchAPIView.as_view(), - dict(fetchtype="all"), # TODO: shouldn't this be "many"? + dict(fetchtype="many"), name="advanced-cursor-fetch-many", ), re_path( From c2522dd4a209d79f4acc9e2e14740a5d5e9631a3 Mon Sep 17 00:00:00 2001 From: Christian Winger Date: Thu, 18 Dec 2025 10:30:37 +0100 Subject: [PATCH 3/7] ensure api_exception returns Json --- api/actions.py | 2 -- api/helper.py | 17 ++++++++--------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/api/actions.py b/api/actions.py index 597972fd4..635c3efac 100644 --- a/api/actions.py +++ b/api/actions.py @@ -1968,8 +1968,6 @@ def get_foreign_keys(request: dict, context: dict | None = None) -> dict: def get_indexes(request: dict, context: dict | None = None) -> dict: - # TODO can we remove this endpoint - table_obj = table_or_404_from_dict(request) engine = _get_engine() diff --git a/api/helper.py b/api/helper.py index c767e2aaf..c30cda0c6 100644 --- a/api/helper.py +++ b/api/helper.py @@ -27,10 +27,11 @@ from decimal import Decimal from typing import Callable, Union -import geoalchemy2 # noqa: Although this import seems unused is has to be here +import geoalchemy2 # noqa:F401 Although this import seems unused is has to be here import psycopg2 from django.core.exceptions import ObjectDoesNotExist from django.http import HttpRequest, JsonResponse, StreamingHttpResponse +from django.http.response import Http404 from django.utils import timezone from rest_framework import status from rest_framework.request import Request @@ -226,15 +227,13 @@ def wrapper(*args, **kwargs): return f(*args, **kwargs) except APIError as e: return JsonResponse({"reason": e.message}, status=e.status) - except Table.DoesNotExist: + except (Table.DoesNotExist, Http404): return JsonResponse({"reason": "table does not exist"}, status=404) - - # TODO: why cant' we handle all other errors here? (tests failing) - # except Exception as exc: - # # All other Errors: dont accidently return sensitive data from error - # # but return generic error message - # logger.error(str(exc)) - # return JsonResponse({"reason": "Invalid request."}, status=400) + except Exception as exc: + # All other Errors: dont accidently return sensitive data from error + # but return generic error message + logger.error(str(exc)) + return JsonResponse({"reason": f"{type(exc)}: {exc}"}, status=400) return wrapper From ff8761a2c3c85598cfeb186d97c15b7ce6ca020d Mon Sep 17 00:00:00 2001 From: Christian Winger Date: Thu, 18 Dec 2025 10:34:18 +0100 Subject: [PATCH 4/7] license as meta variable --- api/__init__.py | 2 +- api/apps.py | 2 +- api/encode.py | 2 +- api/error.py | 2 +- api/helper.py | 2 +- api/parser.py | 15 ++++----------- api/sessions.py | 3 ++- api/utils.py | 2 ++ 8 files changed, 13 insertions(+), 17 deletions(-) diff --git a/api/__init__.py b/api/__init__.py index b423b8d9f..bf8cab862 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -1,4 +1,4 @@ -""" +__license__ = """ SPDX-FileCopyrightText: 2025 Christian Winger © Öko-Institut e.V. SPDX-FileCopyrightText: 2025 Martin Glauer © Otto-von-Guericke-Universität Magdeburg SPDX-FileCopyrightText: 2025 Martin Glauer © Otto-von-Guericke-Universität Magdeburg diff --git a/api/apps.py b/api/apps.py index d32b9baaf..527566568 100644 --- a/api/apps.py +++ b/api/apps.py @@ -1,4 +1,4 @@ -""" +__license__ = """ SPDX-FileCopyrightText: 2025 Martin Glauer © Otto-von-Guericke-Universität Magdeburg SPDX-FileCopyrightText: 2025 Martin Glauer © Otto-von-Guericke-Universität Magdeburg SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/api/encode.py b/api/encode.py index af2352517..7e00eb661 100644 --- a/api/encode.py +++ b/api/encode.py @@ -1,4 +1,4 @@ -""" +__license__ = """ SPDX-FileCopyrightText: 2025 Christian Winger © Öko-Institut e.V. SPDX-FileCopyrightText: 2025 Martin Glauer © Otto-von-Guericke-Universität Magdeburg SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/api/error.py b/api/error.py index 0a02232f7..138f83314 100644 --- a/api/error.py +++ b/api/error.py @@ -1,4 +1,4 @@ -""" +__license__ = """ SPDX-FileCopyrightText: 2025 Martin Glauer © Otto-von-Guericke-Universität Magdeburg SPDX-FileCopyrightText: 2025 Martin Glauer © Otto-von-Guericke-Universität Magdeburg SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/api/helper.py b/api/helper.py index c30cda0c6..4a5281d34 100644 --- a/api/helper.py +++ b/api/helper.py @@ -1,4 +1,4 @@ -""" +__license__ = """ SPDX-FileCopyrightText: 2025 Adel Memariani © Otto-von-Guericke-Universität Magdeburg SPDX-FileCopyrightText: 2025 Adel Memariani © Otto-von-Guericke-Universität Magdeburg SPDX-FileCopyrightText: 2025 Christian Winger © Öko-Institut e.V. diff --git a/api/parser.py b/api/parser.py index afdebd853..e08722e54 100644 --- a/api/parser.py +++ b/api/parser.py @@ -1,4 +1,4 @@ -""" +__license__ = """ SPDX-FileCopyrightText: 2025 Christian Winger © Öko-Institut e.V. SPDX-FileCopyrightText: 2025 Johann Wagner © Otto-von-Guericke-Universität Magdeburg SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut @@ -107,15 +107,9 @@ def get_column_definition_query(d: dict) -> Column: d["autoincrement"] = True for fk in d.get("foreign_key", []): - table_obj = table_or_404_from_dict(fk) - - # NOTE: previously, this used a sqlalchemy MetaData without bind=engine. - # could this be a problem? fk_sa_table = table_obj.get_oedb_table_proxy()._main_table.get_sa_table() - fkcolumn = Column(get_or_403(fk, "column")) - fkcolumn.table = fk_sa_table args.append(ForeignKey(fkcolumn)) @@ -285,7 +279,6 @@ def set_meta_info(method, user, message=None) -> dict: def parse_insert( sa_table_insert: "SATable", d: dict, context: dict, message=None, mapper=None ): - field_strings = [] for field in d.get("fields", []): if not ( @@ -426,7 +419,6 @@ def parse_select(d: dict): elif type.lower() == "except": query.except_(subquery) if "order_by" in d: - for ob in d["order_by"]: expr = parse_expression(ob) @@ -890,8 +882,9 @@ def _parse_sqla_operator(raw_key, *operands): return x.distance_centroid(y) if key in ["getitem"]: if isinstance(y, Slice): - ystart, ystop = _parse_single(y.start, int), _parse_single( - y.stop, int + ystart, ystop = ( + _parse_single(y.start, int), + _parse_single(y.stop, int), ) return x[ystart:ystop] else: diff --git a/api/sessions.py b/api/sessions.py index 64a3778ff..672013149 100644 --- a/api/sessions.py +++ b/api/sessions.py @@ -1,5 +1,6 @@ -"""This module handles all relevant features that belong to specific sessions. +"""This module handles all relevant features that belong to specific sessions.""" +__license__ = """ SPDX-FileCopyrightText: 2025 Christian Winger © Öko-Institut e.V. SPDX-FileCopyrightText: 2025 Martin Glauer © Otto-von-Guericke-Universität Magdeburg SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/api/utils.py b/api/utils.py index 0254ebcc2..b1b549ab4 100644 --- a/api/utils.py +++ b/api/utils.py @@ -1,7 +1,9 @@ """ Collection of utility functions for the API used to define various action like processing steps. +""" +__license__ = """ SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut SPDX-License-Identifier: AGPL-3.0-or-later From 6187e90dede1cb9382771beaaf1b456448da05ff Mon Sep 17 00:00:00 2001 From: Christian Winger Date: Thu, 18 Dec 2025 11:07:57 +0100 Subject: [PATCH 5/7] combine sqlalchemy execute methods --- api/actions.py | 95 +++++++++++++++----------------------------------- 1 file changed, 28 insertions(+), 67 deletions(-) diff --git a/api/actions.py b/api/actions.py index 635c3efac..f4d1bb080 100644 --- a/api/actions.py +++ b/api/actions.py @@ -155,7 +155,7 @@ def assert_add_tag_permission(user, table_obj: Table, permission: int) -> None: def translate_fetched_cell(cell): if isinstance(cell, geoalchemy2.WKBElement): - return new_engine_execute(cell.ST_AsText()).scalar() + return _execute(_get_engine(), cell.ST_AsText()).scalar() elif isinstance(cell, memoryview): return wkb.dumps(wkb.loads(cell.tobytes()), hex=True) else: @@ -323,13 +323,9 @@ def describe_columns(table_obj: Table): table=table_obj.name, schema=table_obj.oedb_schema ) ) - response = session_execute(session, query) - - # Note: cast is only for type checking, - # should disappear once we migrate to sqlalchemy >= 1.4 - response = cast(ResultProxy, response) - + response = _execute(session, query) session.close() + return { column.column_name: { "ordinal_position": column.ordinal_position, @@ -373,7 +369,7 @@ def describe_indexes(table_obj: Table): table=table_obj.name, schema=table_obj.oedb_schema ) ) - response = session_execute(session, query) + response = _execute(session, query) session.close() # Use a single-value dictionary to allow future extension with downward @@ -404,7 +400,7 @@ def describe_constraints(table_obj: Table): query = "select constraint_name, constraint_type, is_deferrable, initially_deferred, pg_get_constraintdef(c.oid) as definition from information_schema.table_constraints JOIN pg_constraint AS c ON c.conname=constraint_name where table_name='{table}' AND constraint_schema='{schema}';".format( # noqa table=table_obj.name, schema=table_obj.oedb_schema ) - response = session_execute(session, query) + response = _execute(session, query) session.close() return { column.constraint_name: { @@ -434,7 +430,7 @@ def perform_sql(sql_statement, parameter: dict | None = None) -> dict: return get_response_dict(success=True) try: - result = session_execute_parameter(session, sql_statement, parameter) + result = _execute(session, sql_statement, parameter) except Exception as e: logger.error("SQL Action failed. \n Error:\n" + str(e)) session.rollback() @@ -663,7 +659,7 @@ def get_column_changes(reviewed=None, changed=None, table_obj: Table | None = No sql = "".join(query) - response = session_execute(session, sql) + response = _execute(session, sql) session.close() return [ @@ -716,7 +712,7 @@ def get_constraints_changes( sql = "".join(query) - response = session_execute(session, sql) + response = _execute(session, sql) session.close() return [ @@ -1043,7 +1039,7 @@ def _drop_not_null_constraints_from_delete_meta_table( AND table_schema = '{meta_schema}' AND is_nullable = 'NO' """ - resp = new_engine_execute(query).fetchall() + resp = _execute(_get_engine(), query).fetchall() column_names = [x[0] for x in resp] if resp else [] # filter meta columns and id column_names = [ @@ -1057,7 +1053,7 @@ def _drop_not_null_constraints_from_delete_meta_table( # drop not null from these columns col_drop = ", ".join(f'ALTER "{c}" DROP NOT NULL' for c in column_names) query = f'ALTER TABLE "{meta_schema}"."{meta_table_delete}" {col_drop};' - new_engine_execute(query) + _execute(_get_engine(), query) def data_insert_check(table_obj: Table, values, context): @@ -1075,7 +1071,7 @@ def data_insert_check(table_obj: Table, values, context): table=table_obj.name, schema=table_obj.oedb_schema ) ) - response = session_execute(session, query) + response = _execute(session, query) session.close() for constraint in response: @@ -1090,7 +1086,7 @@ def data_insert_check(table_obj: Table, values, context): # Use joins instead to avoid piping your results through # python. if isinstance(values, Select): - values = new_engine_execute(values) + values = _execute(_get_engine(), values) for row in values: # TODO: This is horribly inefficient! query = { @@ -1177,7 +1173,7 @@ def execute_sqla(query, cursor: AbstractCursor | Session) -> None: params[key] = json.dumps(value) else: params[key] = dialect._json_serializer(value) - cursor_execute_parameter(cursor, str(compiled), params) + _execute(cursor, str(compiled), params) except (psycopg2.DataError, exc.IdentifierError, psycopg2.IntegrityError) as e: raise APIError(str(e)) except psycopg2.InternalError as e: @@ -1207,7 +1203,8 @@ def execute_sqla(query, cursor: AbstractCursor | Session) -> None: def analyze_columns(table_obj: Table): - result = new_engine_execute( + result = _execute( + _get_engine(), "select column_name as id, data_type as type from information_schema.columns where table_name = '{table}' and table_schema='{schema}';".format( # noqa schema=table_obj.oedb_schema, table=table_obj.name ), @@ -1350,7 +1347,7 @@ def add_type(d, type): oedb_table = table_obj.get_oedb_table_proxy(user=None) insert_sa_table = oedb_table._insert_table.get_sa_table() - cursor_execute( + _execute( cursor, 'select * from "{schema}"."{table}" where _applied = FALSE;'.format( schema=insert_sa_table.schema, table=insert_sa_table.name @@ -1369,7 +1366,7 @@ def add_type(d, type): ] update_sa_table = oedb_table._edit_table.get_sa_table() - cursor_execute( + _execute( cursor, 'select * from "{schema}"."{table}" where _applied = FALSE;'.format( schema=update_sa_table.schema, table=update_sa_table.name @@ -1388,7 +1385,7 @@ def add_type(d, type): ] delete_sa_table = oedb_table._delete_table.get_sa_table() - cursor_execute( + _execute( cursor, 'select * from "{schema}"."{table}" where _applied = FALSE;'.format( schema=delete_sa_table.schema, table=delete_sa_table.name @@ -1466,7 +1463,7 @@ def set_applied( .values(_applied=True) .compile() ) - session_execute_parameter(session, str(update_query), update_query.params) + _execute(session, str(update_query), update_query.params) def apply_insert(session: AbstractCursor | Session, sa_table: "SATable", rows, rids): @@ -1586,7 +1583,7 @@ def get_single_table_size(table_obj: Table) -> dict | None: sess = _create_oedb_session() try: - res = session_execute_parameter( + res = _execute( sess, sql, {"schema": table_obj.oedb_schema, "table": table_obj.name} ) row = res.fetchone() @@ -1623,7 +1620,7 @@ def list_table_sizes() -> list[dict]: sess = _create_oedb_session() try: - res = session_execute(sess, sql) + res = _execute(sess, sql) rows = res.fetchall() or [] out = [] for r in rows: @@ -1648,7 +1645,7 @@ def table_has_row_with_id(table: Table, id: int | str, id_col: str = "id") -> bo engine = _get_engine() with engine.connect() as conn: - resp = connection_execute(conn, query, id=id) + resp = _execute(conn, query, id=id) row = resp.fetchone() row_count = row[0] if row else 0 @@ -1666,7 +1663,7 @@ def table_get_row_count(table: Table) -> int: engine = _get_engine() with engine.connect() as conn: - resp = connection_execute(conn, query) + resp = _execute(conn, query) row = resp.fetchone() row_count = row[0] if row else 0 @@ -1699,7 +1696,7 @@ def table_get_approx_row_count(table: Table, precise_below: int = 0) -> int: ) with engine.connect() as conn: - resp = connection_execute(conn, query) + resp = _execute(conn, query) row = resp.fetchone() row_count = row[0] if row else 0 @@ -1906,7 +1903,7 @@ def get_columns(request: dict, context: dict | None = None) -> dict: bindparams=[sql.bindparam("table_oid", type_=sa_types.Integer)], typemap={"attname": sa_types.Unicode, "default": sa_types.Unicode}, ) - c = connection_execute(connection, s, table_oid=table_oid) + c = _execute(connection, s, table_oid=table_oid) rows = c.fetchall() or [] domains = engine.dialect._load_domains(connection) @@ -2112,47 +2109,11 @@ def fetchone(request: dict, context: dict) -> list | None: # ------------------------------------------------------------------------------------- -def session_execute(session: Session, sql) -> ResultProxy: - response = session.execute(sql) - # Note: cast is only for type checking, - # should disappear once we migrate to sqlalchemy >= 1.4 - response = cast(ResultProxy, response) - return response - - -def session_execute_parameter( - session: AbstractCursor | Session, sql, parameter +def _execute( + con: Session | Engine | Connection | AbstractCursor, sql, *args, **kwargs ) -> ResultProxy: - response = session.execute(sql, parameter) - # Note: cast is only for type checking, - # should disappear once we migrate to sqlalchemy >= 1.4 - response = cast(ResultProxy, response) - return response - - -def new_engine_execute(sql) -> ResultProxy: - return engine_execute(_get_engine(), sql) - - -def engine_execute(engine: Engine, sql) -> ResultProxy: - response = engine.execute(sql) + response = con.execute(sql, *args, **kwargs) # Note: cast is only for type checking, # should disappear once we migrate to sqlalchemy >= 1.4 response = cast(ResultProxy, response) return response - - -def connection_execute(connection: Connection, sql, **kwargs) -> ResultProxy: - response = connection.execute(sql, **kwargs) - # Note: cast is only for type checking, - # should disappear once we migrate to sqlalchemy >= 1.4 - response = cast(ResultProxy, response) - return response - - -def cursor_execute(cursor: AbstractCursor, sql) -> None: - cursor.execute(sql) - - -def cursor_execute_parameter(cursor: AbstractCursor | Session, sql, parameter) -> None: - cursor.execute(sql, parameter) From 115bc9bcb4685ea924bd50e5152aed3ef4afa2e4 Mon Sep 17 00:00:00 2001 From: Christian Winger Date: Thu, 18 Dec 2025 12:00:59 +0100 Subject: [PATCH 6/7] type hints / error message --- api/actions.py | 16 +++++++++++----- api/helper.py | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/api/actions.py b/api/actions.py index f4d1bb080..32c0eb31b 100644 --- a/api/actions.py +++ b/api/actions.py @@ -49,7 +49,7 @@ ) from sqlalchemy import types as sa_types from sqlalchemy.exc import NoSuchTableError -from sqlalchemy.sql.expression import Select +from sqlalchemy.sql.expression import Executable, Select import dataedit.metadata from api.error import APIError @@ -1173,7 +1173,8 @@ def execute_sqla(query, cursor: AbstractCursor | Session) -> None: params[key] = json.dumps(value) else: params[key] = dialect._json_serializer(value) - _execute(cursor, str(compiled), params) + query = str(compiled) + _execute(cursor, query, params) except (psycopg2.DataError, exc.IdentifierError, psycopg2.IntegrityError) as e: raise APIError(str(e)) except psycopg2.InternalError as e: @@ -1463,7 +1464,9 @@ def set_applied( .values(_applied=True) .compile() ) - _execute(session, str(update_query), update_query.params) + + query = str(update_query) + _execute(session, query, update_query.params) def apply_insert(session: AbstractCursor | Session, sa_table: "SATable", rows, rids): @@ -2110,9 +2113,12 @@ def fetchone(request: dict, context: dict) -> list | None: def _execute( - con: Session | Engine | Connection | AbstractCursor, sql, *args, **kwargs + con: Session | Engine | Connection | AbstractCursor, + sql: Executable | str, + *args, + **kwargs, ) -> ResultProxy: - response = con.execute(sql, *args, **kwargs) + response = con.execute(sql, *args, **kwargs) # type:ignore # Note: cast is only for type checking, # should disappear once we migrate to sqlalchemy >= 1.4 response = cast(ResultProxy, response) diff --git a/api/helper.py b/api/helper.py index 4a5281d34..a1195aec4 100644 --- a/api/helper.py +++ b/api/helper.py @@ -233,7 +233,7 @@ def wrapper(*args, **kwargs): # All other Errors: dont accidently return sensitive data from error # but return generic error message logger.error(str(exc)) - return JsonResponse({"reason": f"{type(exc)}: {exc}"}, status=400) + return JsonResponse({"reason": "Invalid request"}, status=400) return wrapper From 545f695855e1b4f8600de4e5e2fef95559e8b816 Mon Sep 17 00:00:00 2001 From: Christian Winger Date: Tue, 23 Dec 2025 14:34:52 +0100 Subject: [PATCH 7/7] Squashed commit of the following: commit d25b710b7e8352f48b7add288b55ad1b1bdbfb72 Author: Christian Winger Date: Tue Dec 23 12:03:11 2025 +0100 EOL commit b5a72aee9941ada253b41e532aea8032139fcf2e Author: Christian Winger Date: Tue Dec 23 11:58:00 2025 +0100 prettierignore - dont ignore frontend anymore commit 8d13bea948ae1daa20505780e465e069002f1251 Merge: 81dcbd13 ff3fb8d0 Author: wingechr Date: Tue Dec 23 11:54:40 2025 +0100 Merge pull request #2199 from OpenEnergyPlatform/issues-upload-wizard Issues upload wizard commit ff3fb8d082a0e2a9e16e683268452d2b6a092a8b Author: Christian Winger Date: Tue Dec 23 11:50:38 2025 +0100 changelog commit 4d5d489b5eac6516ad43e82d05f413d0159ee66e Author: Christian Winger Date: Tue Dec 23 11:49:16 2025 +0100 fomatting commit a4fb64378b719d5b81db1c70a64d6af9f1919097 Author: Christian Winger Date: Tue Dec 23 11:46:56 2025 +0100 removed unused function (deleteTable) commit dd44aa341babfe1db50f71a50d02c5d02f618bbe Author: Christian Winger Date: Tue Dec 23 11:45:56 2025 +0100 commit 81dcbd13d5901fc24b6de4227452117c56d95711 Merge: 8c7bad06 17795cea Author: wingechr Date: Tue Dec 23 08:14:51 2025 +0100 Merge pull request #2200 from OpenEnergyPlatform/snyk-fix-94f7d67e5cb817e36a18d429fad0d441 [Snyk] Security upgrade node from 18 to 25.2.1 commit 17795ceacd9456102173fce25c38822ed359af98 Author: snyk-bot Date: Tue Dec 23 06:34:51 2025 +0000 fix: docker/Dockerfile.vite to reduce vulnerabilities The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-DEBIAN12-IMAGEMAGICK-10752987 - https://snyk.io/vuln/SNYK-DEBIAN12-IMAGEMAGICK-10752987 - https://snyk.io/vuln/SNYK-DEBIAN12-IMAGEMAGICK-10752988 - https://snyk.io/vuln/SNYK-DEBIAN12-IMAGEMAGICK-12549185 - https://snyk.io/vuln/SNYK-DEBIAN12-IMAGEMAGICK-12549185 commit d1b3d555327b9ef6758fe50b0e7478ac19a4f198 Author: Christian Winger Date: Mon Dec 22 17:27:32 2025 +0100 commit 5ae9fc61db20185796db571e93a20bbb04ba6e47 Author: Christian Winger Date: Mon Dec 22 17:17:44 2025 +0100 commit e5b842002852cea524a0704abc289c2ceb6c70b6 Author: Christian Winger Date: Mon Dec 22 17:15:38 2025 +0100 wizard.js via vite commit 21eb60ea8e6f6773ab7546fa1e3d8cc8682e090a Author: Christian Winger Date: Mon Dec 22 17:07:07 2025 +0100 remove delete button from upload wizard commit f9a94273d96f814e263ffc1257959a4c372f821c Author: Christian Winger Date: Mon Dec 22 17:05:54 2025 +0100 margins commit d7a49e2bd9f67955f8a39cae3929708148fefea1 Merge: 7a61b837 4c5a2448 Author: Christian Winger Date: Mon Dec 22 16:28:35 2025 +0100 Merge branch 'develop' into issues-upload-wizard commit 4c5a2448badf116d03847746d5bd5f6702c0d520 Author: Christian Winger Date: Mon Dec 22 15:51:45 2025 +0100 fix docstrings commit 8c7bad0692be8479e3c77ec4aa4d80a02fed38ed Merge: a4dc92ba 3490c7b1 Author: wingechr Date: Mon Dec 22 12:09:27 2025 +0100 Merge pull request #2196 from OpenEnergyPlatform/issue-2169-pagination-filter Issue 2169 pagination filter commit 3490c7b10b5aa86226641a4710bdfefda460ed36 Author: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon Dec 22 11:05:42 2025 +0000 [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci commit 7d2b6bb1e234172c13097af2ddc0f019ded1873c Author: Christian Winger Date: Mon Dec 22 12:02:36 2025 +0100 changelog commit 7ed10dc5f3277f81778038078dbd096fe9d23667 Author: Christian Winger Date: Mon Dec 22 12:02:12 2025 +0100 reset ITEMS_PER_PAGE tooriginal value commit 7503c07f0053f5b5a5c2b4f0196afd75c7aef8e8 Author: Christian Winger Date: Mon Dec 22 12:01:17 2025 +0100 commit bf2ecdbc6825412374195f2d3eb4be9bef1115ce Author: Christian Winger Date: Mon Dec 22 11:42:31 2025 +0100 don't show tag edit section if user does not have admin rights on table. commit a4dc92baa658bbaf3f55e5ac6bdb34251af5637c Author: Christian Winger Date: Mon Dec 22 10:17:28 2025 +0100 changelog commit bf03afbf390cc1d271934d58ff6e4b1ba787dcd2 Author: Christian Winger Date: Mon Dec 22 10:17:06 2025 +0100 commit 9645b8506280f6bd9957cb0489ba7bbf342ad160 Author: Christian Winger Date: Mon Dec 22 09:48:57 2025 +0100 changelog commit b71629318f50e7062eb6b3062f0fdc535b9af4df Author: Christian Winger Date: Mon Dec 22 09:46:40 2025 +0100 commit bf154979da5c4eefbbb638e0ec2a427a433bd99b Author: Christian Winger Date: Mon Dec 22 09:20:44 2025 +0100 add @never_cache to all GET views in api that didnot have them commit 588cd6e3e1636e7ceb0136d8409f5fe780388465 Merge: 366c8180 6c5a53f9 Author: wingechr Date: Fri Dec 19 19:24:13 2025 +0100 Merge pull request #2195 from OpenEnergyPlatform/issue-1974 closes #1974 commit 6c5a53f986b42139329d966d6499195d00dcfad2 Author: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri Dec 19 18:20:44 2025 +0000 [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci commit dee0261e09eddaf4008682e3c0a83e292510cc4b Author: Christian Winger Date: Fri Dec 19 19:19:47 2025 +0100 changelog commit e3cdfb18372b01aef3f7d85afa24372c0529a710 Author: Christian Winger Date: Fri Dec 19 19:19:06 2025 +0100 better sorting: lower case string, sort arrays (list of dates) commit 7da53064cc5842e83ff422fc26a60ec387891ff2 Author: Christian Winger Date: Fri Dec 19 18:31:06 2025 +0100 sorting does not work if ids have spaces commit e08d95793ad13a843ef0da1321abba2c79f0027f Author: Christian Winger Date: Fri Dec 19 18:26:13 2025 +0100 isRequired caused exception in browser when loading page commit 8da2e9b2469b801bda91c4986cee000dc619ae27 Author: Christian Winger Date: Fri Dec 19 18:22:14 2025 +0100 formatting commit 366c81807df83aa64c0f362876a5a55e6e7b2a83 Merge: 6c625862 f20edb49 Author: wingechr Date: Fri Dec 19 17:02:39 2025 +0100 Merge pull request #2194 from OpenEnergyPlatform/issues-bundles-2102-2091 Issues bundles 2102 2091 commit f20edb4923d9577e09a2839c37f9f3a0950b7b25 Author: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri Dec 19 16:01:56 2025 +0000 [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci commit 0800856c1f263e3653c320702edb7f633a83a304 Author: Christian Winger Date: Fri Dec 19 16:51:16 2025 +0100 changelog commit fc06567ababd32c56e3931d36f77e4c7cbc21f32 Author: Christian Winger Date: Fri Dec 19 16:50:24 2025 +0100 commit 7c2446b5e31c12d8f56f733f77910aa6b0a2be4a Author: Christian Winger Date: Fri Dec 19 15:38:04 2025 +0100 changelog commit 6c6258629cc53e8005e617e996f331c88e512a1d Merge: 06473c5d 2629120c Author: wingechr Date: Fri Dec 19 15:31:55 2025 +0100 Merge pull request #2193 from OpenEnergyPlatform/issue-2038 close #2038 commit 2629120c4ff4ee7c3252859bb709e42d6f7d3f90 Author: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri Dec 19 14:28:29 2025 +0000 [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci commit 9d2268fd77ef29f289f4b542cee29b2340d76a0d Author: Christian Winger Date: Fri Dec 19 15:26:39 2025 +0100 properly indent oe meta edit app commit 428b017013456092c9254cea0530d2919cb9a846 Author: Christian Winger Date: Fri Dec 19 15:26:26 2025 +0100 fix undeclared variables commit cbb487deff6cd32d952f0aa8b8df92054981d389 Author: Christian Winger Date: Fri Dec 19 15:04:38 2025 +0100 commit 42595d6138e0c68aace37cc839f59064779ebdca Author: Christian Winger Date: Fri Dec 19 15:01:30 2025 +0100 code quality,load metaedit.js with vite commit 1e040211180c60bbd61897e90415d5cbe17d22b1 Author: Christian Winger Date: Fri Dec 19 14:25:48 2025 +0100 formatting commit 7a61b837b850e8967ce674ffb564d36012a56441 Merge: 4fd0620f 41f971b7 Author: Christian Winger Date: Thu Nov 27 16:48:19 2025 +0100 Merge branch 'develop' into issues-upload-wizard commit 4fd0620f43a0802b56a96da2aa908d2f96243bef Author: Christian Winger Date: Thu Nov 27 12:03:57 2025 +0100 harmonize wizard html template with topic view commit 8962c03d88adc80a902aa46b606d976488aef2fb Author: Christian Winger Date: Thu Nov 27 11:20:48 2025 +0100 wizard: start to separate create and upload commit 1aa60f12d443f4fdaefaa6a55933e560a1309da5 Author: Christian Winger Date: Thu Nov 27 11:19:33 2025 +0100 harmonize wizard html template with topic view commit 55367ebbdb597403d00a3e97602f57efb48f9b73 Author: Christian Winger Date: Thu Nov 27 10:50:45 2025 +0100 code quality commit ed341bb53ac481cefecd3990663940fbf19275a2 Author: Christian Winger Date: Mon Nov 24 19:15:57 2025 +0100 fixed js error commit 955c20dde3f5a056c15417c76d4fd51f8656c766 Author: Christian Winger Date: Mon Nov 24 18:03:15 2025 +0100 --- .prettierignore | 8 - api/views.py | 6 + dataedit/static/metaedit/metaedit.js | 388 ++++++++++------- dataedit/static/wizard/wizard.js | 119 +++--- .../templates/dataedit/base_fullwidth.html | 3 + .../dataedit/dataedit_tablelist.html | 7 +- dataedit/templates/dataedit/dataview.html | 2 +- dataedit/templates/dataedit/meta_edit.html | 187 +++++---- .../templates/dataedit/taggable_setting.html | 62 +-- dataedit/templates/dataedit/wizard.html | 180 +++----- dataedit/views.py | 32 +- docker/Dockerfile.vite | 2 +- .../src/components/FactsheetFilterDialog.jsx | 24 +- .../src/components/comparisonBoardItems.jsx | 308 +++++++------- .../src/components/comparisonControl.jsx | 32 +- .../src/components/customAutocomplete.jsx | 18 +- .../customAutocompleteWithoutAddNew.jsx | 10 +- .../customAutocompleteWithoutEdit.jsx | 24 +- .../frontend/src/components/customCard.jsx | 4 +- .../src/components/customDatePicker.jsx | 2 +- .../src/components/customSearchInput.jsx | 20 +- .../frontend/src/components/customTable.jsx | 390 +++++++++--------- .../src/components/oekg_modifications.jsx | 62 +-- .../src/components/scenarioBundle.tsx | 22 +- .../handleOnClickTableIRI.jsx | 62 +-- factsheet/oekg/filters.py | 10 +- login/views.py | 18 +- versions/changelogs/current.md | 8 + 28 files changed, 1051 insertions(+), 959 deletions(-) diff --git a/.prettierignore b/.prettierignore index ee08d7cd5..83a6769a8 100644 --- a/.prettierignore +++ b/.prettierignore @@ -6,11 +6,3 @@ mkdocs.yml # django templates will be handled by djlint */templates/**/*.html - -# ignore these for now -**/*.js -**/*.tsx -**/*.jsx -**/*.css -**/*.scss -**/*.jsonc diff --git a/api/views.py b/api/views.py index 9074747c2..2e6757f4e 100644 --- a/api/views.py +++ b/api/views.py @@ -877,6 +877,7 @@ def __get_rows(self, request: Request, table_obj: Table, data): @api_exception +@never_cache def table_approx_row_count_view(request: HttpRequest, table: str) -> JsonResponse: table_obj = table_or_404(table=table) precise_below = int( @@ -890,6 +891,7 @@ def table_approx_row_count_view(request: HttpRequest, table: str) -> JsonRespons @api_exception +@never_cache def usrprop_api_view(request: Request) -> JsonLikeResponse: query = request.GET.get("name", "") @@ -914,6 +916,7 @@ def usrprop_api_view(request: Request) -> JsonLikeResponse: return JsonResponse(user_names, safe=False) +@never_cache @api_exception def grpprop_api_view(request: Request) -> JsonLikeResponse: """ @@ -949,6 +952,7 @@ def grpprop_api_view(request: Request) -> JsonLikeResponse: return JsonResponse(group_names, safe=False) +@never_cache @api_exception def oeo_search_api_view(request: Request) -> JsonLikeResponse: if USE_LOEP: @@ -967,6 +971,7 @@ def oeo_search_api_view(request: Request) -> JsonLikeResponse: return JsonResponse(res, safe=False) +@never_cache @api_exception def oevkg_query_api_view(request: Request) -> JsonLikeResponse: if USE_ONTOP and ONTOP_SPARQL_ENDPOINT_URL: @@ -1094,6 +1099,7 @@ class AllTableSizesAPIView(APIView): """ @api_exception + @method_decorator(never_cache) def get(self, request: Request) -> JsonLikeResponse: table = request.query_params.get("table") diff --git a/dataedit/static/metaedit/metaedit.js b/dataedit/static/metaedit/metaedit.js index 2bd0840cc..7bbf46de7 100644 --- a/dataedit/static/metaedit/metaedit.js +++ b/dataedit/static/metaedit/metaedit.js @@ -1,15 +1,17 @@ -// SPDX-FileCopyrightText: 2025 Adel Memariani © Otto-von-Guericke-Universität Magdeburg -// SPDX-FileCopyrightText: 2025 Christian Winger © Öko-Institut e.V. -// SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut -// SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut -// SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut -// -// SPDX-License-Identifier: AGPL-3.0-or-later - -// e.preventDefault(), e.stopPropagation(), t.saveJSON() - - -var MetaEdit = function(config) { +/* eslint-disable max-len */ +/* +SPDX-FileCopyrightText: 2025 Adel Memariani © Otto-von-Guericke-Universität Magdeburg +SPDX-FileCopyrightText: 2025 Christian Winger © Öko-Institut e.V. +SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut +SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut +SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut +SPDX-License-Identifier: AGPL-3.0-or-later +*/ +/* eslint-enable max-len */ + +"use strict"; + +window.MetaEdit = function (config) { /* TODO: consolidate functions (same as in wizard and other places) */ @@ -38,7 +40,7 @@ var MetaEdit = function(config) { var token = getCsrfToken(); return $.ajax({ url: url, - headers: {"X-CSRFToken": token}, + headers: { "X-CSRFToken": token }, data_type: "json", cache: false, contentType: "application/json; charset=utf-8", @@ -50,21 +52,21 @@ var MetaEdit = function(config) { }); } - function fixSchema(json) { /* recursively remove null types */ function fixRecursive(elem) { - Object.keys(elem).map(function(key) { + Object.keys(elem).map(function (key) { var prop = elem[key]; prop.title = prop.title || key[0].toLocaleUpperCase() + key.slice(1); - if (prop.type == 'array') { - // prop.items = prop.items || {}; - prop.items.title = prop.items.title || key[0].toLocaleUpperCase() + key.slice(1); // missing title, otherwise the form label is just "item 1, ..." - fixRecursive({"": prop.items}); - } else if (prop.type == 'object') { + if (prop.type == "array") { + /* missing title, otherwise the form label is just "item 1, ..." */ + prop.items.title = + prop.items.title || key[0].toLocaleUpperCase() + key.slice(1); + fixRecursive({ "": prop.items }); + } else if (prop.type == "object") { // prop.properties = prop.properties || {}; fixRecursive(prop.properties); - } else if (typeof prop.type == 'object') { + } else if (typeof prop.type == "object") { // find and remove "null" var index = prop.type.indexOf("null"); if (index >= 0) { @@ -80,36 +82,45 @@ var MetaEdit = function(config) { fixRecursive(json.properties); // make some readonly + var fieldProperties = + json.properties.resources.items.properties.schema.properties.fields.items + .properties; if (config.standalone == false) { json.properties["@id"].readOnly = false; - json.properties.resources.items.properties.schema.properties.fields.items.properties.name.readOnly = true; - json.properties.resources.items.properties.schema.properties.fields.items.properties.type.readOnly = true; + fieldProperties.name.readOnly = true; + fieldProperties.type.readOnly = true; } else { - json.properties["@context"].options = {hidden: true}; + json.properties["@context"].options = { hidden: true }; json.properties["@id"].readOnly = false; json.properties.resources.items.properties["@id"].readOnly = false; json.properties.resources.items.properties.path.readOnly = false; - json.properties.resources.items.properties.schema.properties.fields.items.properties.nullable.readOnly = false; - json.properties.resources.items.properties.schema.properties.fields.items.properties.name.readOnly = false; - json.properties.resources.items.properties.schema.properties.fields.items.properties.type.readOnly = false; + fieldProperties.nullable.readOnly = false; + fieldProperties.name.readOnly = false; + fieldProperties.type.readOnly = false; } // remove some: TODO: but make sure fields are not lost // json.properties.resources.items.properties.embargoPeriod = false; // hide some - json.properties.resources.items.properties.embargoPeriod.options = {hidden: true}; - json.properties.resources.items.properties.type.options = {hidden: true}; - json.properties.resources.items.properties.encoding.options = {hidden: true}; - json.properties.resources.items.properties.dialect.options = {hidden: true}; + json.properties.resources.items.properties.embargoPeriod.options = { + hidden: true, + }; + json.properties.resources.items.properties.type.options = { hidden: true }; + json.properties.resources.items.properties.encoding.options = { + hidden: true, + }; + json.properties.resources.items.properties.dialect.options = { + hidden: true, + }; // json.properties.resources.items.properties.review.options = {hidden: true}; - json.properties.metaMetadata.options = {hidden: true}; + json.properties.metaMetadata.options = { hidden: true }; // add formats - // json.properties.context.properties.homepage.format = 'url'; // uri or url??? + // json.properties.context.properties.homepage.format = 'url'; // uri or url??? json["options"] = { - "disable_edit_json": false, // show only for entire form + disable_edit_json: false, // show only for entire form }; if (config.standalone == false) { @@ -119,7 +130,7 @@ var MetaEdit = function(config) { } // add names to resources categories - json.properties.resources.items.basicCategoryTitle = "General" + json.properties.resources.items.basicCategoryTitle = "General"; return json; } @@ -128,7 +139,10 @@ var MetaEdit = function(config) { json = json || {}; // Required top-level fields - json["@context"] = json["@context"] || "https://raw.githubusercontent.com/OpenEnergyPlatform/oemetadata/production/oemetadata/latest/context.json"; + json["@context"] = + json["@context"] || + // eslint-disable-next-line max-len + "https://raw.githubusercontent.com/OpenEnergyPlatform/oemetadata/production/oemetadata/latest/context.json"; json.metaMetadata = json.metaMetadata || {}; if (!json.metaMetadata.metadataVersion) { @@ -150,12 +164,12 @@ var MetaEdit = function(config) { const fieldMap = new Map(); // Add existing fields to a map for quick lookup - existingFields.forEach(field => { + existingFields.forEach((field) => { if (field.name) fieldMap.set(field.name, field); }); // Prepare updated fields array - const updatedFields = config.columns.map(col => { + const updatedFields = config.columns.map((col) => { const existing = fieldMap.get(col.name) || {}; return { @@ -174,14 +188,14 @@ var MetaEdit = function(config) { function fillMissingFromSchema(schemaProps, target) { Object.entries(schemaProps).forEach(([key, prop]) => { if (target[key] === undefined) { - if (prop.type === 'object') { + if (prop.type === "object") { target[key] = {}; if (prop.properties) { fillMissingFromSchema(prop.properties, target[key]); } - } else if (prop.type === 'array') { + } else if (prop.type === "array") { target[key] = []; - if (prop.items?.type === 'object' && prop.items.properties) { + if (prop.items?.type === "object" && prop.items.properties) { const obj = {}; fillMissingFromSchema(prop.items.properties, obj); target[key].push(obj); @@ -189,11 +203,19 @@ var MetaEdit = function(config) { } else { target[key] = null; } - } else if (prop.type === 'object' && typeof target[key] === 'object' && prop.properties) { + } else if ( + prop.type === "object" && + typeof target[key] === "object" && + prop.properties + ) { fillMissingFromSchema(prop.properties, target[key]); - } else if (prop.type === 'array' && Array.isArray(target[key]) && prop.items?.properties) { - target[key].forEach(item => { - if (typeof item === 'object') { + } else if ( + prop.type === "array" && + Array.isArray(target[key]) && + prop.items?.properties + ) { + target[key].forEach((item) => { + if (typeof item === "object") { fillMissingFromSchema(prop.items.properties, item); } }); @@ -204,9 +226,13 @@ var MetaEdit = function(config) { fillMissingFromSchema(config.schema.properties, json); // Fix boundingBox in each resource if needed if (Array.isArray(json.resources)) { - json.resources.forEach(resource => { + json.resources.forEach((resource) => { const bboxPath = resource?.spatial?.extent?.boundingBox; - if (!Array.isArray(bboxPath) || bboxPath.length !== 4 || bboxPath.some(val => typeof val !== 'number')) { + if ( + !Array.isArray(bboxPath) || + bboxPath.length !== 4 || + bboxPath.some((val) => typeof val !== "number") + ) { if (!resource.spatial) resource.spatial = {}; if (!resource.spatial.extent) resource.spatial.extent = {}; resource.spatial.extent.boundingBox = [0, 0, 0, 0]; // fallback valid default @@ -219,7 +245,7 @@ var MetaEdit = function(config) { function getErrorMsg(x) { try { - x = 'Upload failed: ' + JSON.parse(x.responseJSON).reason; + x = "Upload failed: " + JSON.parse(x.responseJSON).reason; } catch (e) { x = x.statusText; } @@ -230,9 +256,9 @@ var MetaEdit = function(config) { function convertEmptyStringsToNull(obj) { for (var key in obj) { if (obj.hasOwnProperty(key)) { - if (typeof obj[key] === 'string' && obj[key] === '') { + if (typeof obj[key] === "string" && obj[key] === "") { obj[key] = null; - } else if (typeof obj[key] === 'object') { + } else if (typeof obj[key] === "object") { convertEmptyStringsToNull(obj[key]); } } @@ -241,21 +267,21 @@ var MetaEdit = function(config) { function bindButtons() { // download - $('#metaedit-download').bind('click', function downloadMetadata() { + $("#metaedit-download").bind("click", function downloadMetadata() { var json = config.editor.getValue(); // create data url convertEmptyStringsToNull(json); console.log(json); json = JSON.stringify(json, null, 1); - blob = new Blob([json], {type: "application/json"}), - dataUrl = URL.createObjectURL(blob); + ((blob = new Blob([json], { type: "application/json" })), + (dataUrl = URL.createObjectURL(blob))); // create link var a = document.createElement("a"); document.body.appendChild(a); // assign url and click a.style = "display: none"; a.href = dataUrl; - a.download = config.table + '.metadata.json'; + a.download = config.table + ".metadata.json"; a.click(); // cleanup URL.revokeObjectURL(dataUrl); @@ -263,55 +289,57 @@ var MetaEdit = function(config) { }); // submit - $('#metaedit-submit').bind('click', function sumbmitMetadata() { - $('#metaedit-submitting').removeClass('d-none'); + $("#metaedit-submit").bind("click", function sumbmitMetadata() { + $("#metaedit-submitting").removeClass("d-none"); // config.editor.remove_empty_properties = true; var json = config.editor.getValue(); convertEmptyStringsToNull(json); json = fixData(json); json = JSON.stringify(json); - sendJson("POST", config.url_api_meta, json).then(function() { - window.location = config.url_view_table; - }).catch(function(err) { - // TODO evaluate error, show user message - $('#metaedit-submitting').addClass('d-none'); - alert(getErrorMsg(err)); - }); + sendJson("POST", config.url_api_meta, json) + .then(function () { + window.location = config.url_view_table; + }) + .catch(function (err) { + // TODO evaluate error, show user message + $("#metaedit-submitting").addClass("d-none"); + alert(getErrorMsg(err)); + }); }); // Cancel - $('#metaedit-cancel').bind('click', function cancel() { + $("#metaedit-cancel").bind("click", function cancel() { window.location = config.cancle_url; }); } (function init() { - $('#metaedit-loading').removeClass('d-none'); + $("#metaedit-loading").removeClass("d-none"); - config.form = $('#metaedit-form'); + config.form = $("#metaedit-form"); - // check if the editor should be initialized with metadata from table or as standalone without any initial data + /* check if the editor should be initialized with metadata from table + or as standalone without any initial data*/ if (config.standalone == false) { $.when( - $.getJSON(config.url_api_meta), - $.getJSON('/static/metaedit/schema.json'), - ).done(function(data, schema) { + $.getJSON(config.url_api_meta), + $.getJSON("/static/metaedit/schema.json") + ).done(function (data, schema) { config.schema = fixSchema(schema[0]); config.initialData = fixData(data[0]); - /* https://github.com/json-editor/json-editor */ - options = { + const options = { startval: config.initialData, schema: config.schema, - theme: 'bootstrap5', - iconlib: 'fontawesome5', - mode: 'form', + theme: "bootstrap5", + iconlib: "fontawesome5", + mode: "form", compact: true, disable_collapse: true, prompt_before_delete: false, object_layout: "normal", - disable_properties: false, + disable_properties: true, disable_edit_json: true, disable_array_delete_last_row: true, disable_array_delete_all_rows: true, @@ -319,56 +347,78 @@ var MetaEdit = function(config) { array_controls_top: true, no_additional_properties: true, required_by_default: false, - remove_empty_properties: false, // don't remove, otherwise the metadata will not pass the validation on the server + /* keep remove_empty_properties: false + otherwise the metadata will not pass the validation on the server */ + remove_empty_properties: false, show_errors: "interaction", }; config.editor = new JSONEditor(config.form[0], options); /* patch labels */ - var mainEditBox = config.form.find('.je-object__controls').first(); - mainEditBox.find('.json-editor-btntype-save').text('Apply'); - mainEditBox.find('.json-editor-btntype-copy').text('Copy to Clipboard'); - mainEditBox.find('.json-editor-btntype-cancel').text('Close'); - mainEditBox.find('.json-editor-btntype-editjson').text('Edit raw JSON'); + var mainEditBox = config.form.find(".je-object__controls").first(); + mainEditBox.find(".json-editor-btntype-save").text("Apply"); + mainEditBox.find(".json-editor-btntype-copy").text("Copy to Clipboard"); + mainEditBox.find(".json-editor-btntype-cancel").text("Close"); + mainEditBox.find(".json-editor-btntype-editjson").text("Edit raw JSON"); bindButtons(); // convertDescriptionIntoPopover(); // check for new items in dom - (new MutationObserver(function(_mutationsList, _observer) { + new MutationObserver(function (_mutationsList, _observer) { // convertDescriptionIntoPopover() - })).observe(config.form[0], {attributes: false, childList: true, subtree: true}); + }).observe(config.form[0], { + attributes: false, + childList: true, + subtree: true, + }); // all done - $('#metaedit-loading').addClass('d-none'); - $('#metaedit-icon').removeClass('d-none'); - $('#metaedit-controls').removeClass('d-none'); + $("#metaedit-loading").addClass("d-none"); + $("#metaedit-icon").removeClass("d-none"); + $("#metaedit-controls").removeClass("d-none"); // TODO catch init error window.JSONEditor.defaults.callbacks = { - "autocomplete": { - "search_name": function search(jseditor_editor, input) { - var url = "https://openenergyplatform.org/api/oeo-search?query=" + input; + autocomplete: { + search_name: function search(jseditor_editor, input) { + var url = + "https://openenergyplatform.org/api/oeo-search?query=" + input; - return new Promise(function(resolve) { + return new Promise(function (resolve) { fetch(url, { - mode: 'cors', - }).then(function(response) { - return response.json(); - }).then(function(data) { - resolve(data["docs"]); - }); + mode: "cors", + }) + .then(function (response) { + return response.json(); + }) + .then(function (data) { + resolve(data["docs"]); + }); }); }, - "renderResult_name": function(jseditor_editor, result, props) { - return ['
  • ', - '
    ' + result.label + '' + '' + ' : ' + result.definition + '
    ', - '
  • '].join(''); + renderResult_name: function (jseditor_editor, result, props) { + return [ + "
  • ", + '
    ' + + result.label + + "" + + '' + + " : " + + result.definition + + "
    ", + "
  • ", + ].join(""); }, - "getResultValue_name": function getResultValue(jseditor_editor, result) { - selected_value = String(result.label).replaceAll("", "").replaceAll("", ""); + getResultValue_name: function getResultValue( + jseditor_editor, + result + ) { + selected_value = String(result.label) + .replaceAll("", "") + .replaceAll("", ""); let path = String(jseditor_editor.path).replace("name", "@id"); let path_uri = config.editor.getEditor(path); @@ -377,38 +427,37 @@ var MetaEdit = function(config) { return selected_value; }, }, - "button": { - "openModalAction": function openOeoExtPlugin(jseditor, e) { + button: { + openModalAction: function openOeoExtPlugin(jseditor, e) { // Perform the HTMX request or any other desired action - - htmx.ajax('GET', createUrl, { - target: '.modal-body', - swap: 'innerHTML', - trigger: 'click' + + htmx.ajax("GET", createUrl, { + target: ".modal-body", + swap: "innerHTML", + trigger: "click", }); - $('#formModal').modal('show'); - } - } + $("#formModal").modal("show"); + }, + }, }; }); } else { - $.when( - $.getJSON('/static/metaedit/schema.json'), - ).done(function(schema) { + $.when($.getJSON("/static/metaedit/schema.json")).done(function (schema) { config.schema = fixSchema(schema); - standalone_options = { + const standalone_options = { schema: config.schema, + // eslint-disable-next-line max-len // startval: {"@context": "https://raw.githubusercontent.com/OpenEnergyPlatform/oemetadata/production/oemetadata/latest/context.json"}, - theme: 'bootstrap5', - iconlib: 'fontawesome5', - mode: 'form', + theme: "bootstrap5", + iconlib: "fontawesome5", + mode: "form", compact: true, // remove_button_labels: true, disable_collapse: true, prompt_before_delete: false, object_layout: "normal", - disable_properties: false, + disable_properties: true, disable_edit_json: true, disable_array_delete_last_row: true, disable_array_delete_all_rows: true, @@ -416,54 +465,76 @@ var MetaEdit = function(config) { array_controls_top: true, no_additional_properties: true, required_by_default: false, - remove_empty_properties: false, // don't remove, otherwise the metadata will not pass the validation on the server + /* keep remove_empty_properties: false, + otherwise the metadata will not pass the validation on the server */ + remove_empty_properties: false, }; config.editor = new JSONEditor(config.form[0], standalone_options); /* patch labels */ - var mainEditBox = config.form.find('.je-object__controls').first(); - mainEditBox.find('.json-editor-btntype-save').text('Apply'); - mainEditBox.find('.json-editor-btntype-copy').text('Copy to Clipboard'); - mainEditBox.find('.json-editor-btntype-cancel').text('Close'); - mainEditBox.find('.json-editor-btntype-editjson').text('Edit raw JSON'); + var mainEditBox = config.form.find(".je-object__controls").first(); + mainEditBox.find(".json-editor-btntype-save").text("Apply"); + mainEditBox.find(".json-editor-btntype-copy").text("Copy to Clipboard"); + mainEditBox.find(".json-editor-btntype-cancel").text("Close"); + mainEditBox.find(".json-editor-btntype-editjson").text("Edit raw JSON"); bindButtons(); // convertDescriptionIntoPopover(); // check for new items in dom - (new MutationObserver(function(_mutationsList, _observer) { + new MutationObserver(function (_mutationsList, _observer) { // convertDescriptionIntoPopover() - })).observe(config.form[0], {attributes: false, childList: true, subtree: true}); + }).observe(config.form[0], { + attributes: false, + childList: true, + subtree: true, + }); // all done - $('#metaedit-loading').addClass('d-none'); - $('#metaedit-icon').removeClass('d-none'); - $('#metaedit-controls').removeClass('d-none'); + $("#metaedit-loading").addClass("d-none"); + $("#metaedit-icon").removeClass("d-none"); + $("#metaedit-controls").removeClass("d-none"); // TODO catch init error window.JSONEditor.defaults.callbacks = { - "autocomplete": { - "search_name": function search(jseditor_editor, input) { - var url = "https://openenergyplatform.org/api/oeo-search?query=" + input; + autocomplete: { + search_name: function search(jseditor_editor, input) { + var url = + "https://openenergyplatform.org/api/oeo-search?query=" + input; - return new Promise(function(resolve) { + return new Promise(function (resolve) { fetch(url, { - mode: 'cors', - }).then(function(response) { - return response.json(); - }).then(function(data) { - resolve(data["docs"]); - }); + mode: "cors", + }) + .then(function (response) { + return response.json(); + }) + .then(function (data) { + resolve(data["docs"]); + }); }); }, - "renderResult_name": function(jseditor_editor, result, props) { - return ['
  • ', - '
    ' + result.label + '' + '' + ' : ' + result.definition + '
    ', - '
  • '].join(''); + renderResult_name: function (jseditor_editor, result, props) { + return [ + "
  • ", + '
    ' + + result.label + + "" + + '' + + " : " + + result.definition + + "
    ", + "
  • ", + ].join(""); }, - "getResultValue_name": function getResultValue(jseditor_editor, result) { - selected_value = String(result.label).replaceAll("", "").replaceAll("", ""); + getResultValue_name: function getResultValue( + jseditor_editor, + result + ) { + selected_value = String(result.label) + .replaceAll("", "") + .replaceAll("", ""); let path = String(jseditor_editor.path).replace("name", "@id"); let path_uri = config.editor.getEditor(path); @@ -472,18 +543,18 @@ var MetaEdit = function(config) { return selected_value; }, }, - "button": { - "openModalAction": function openOeoExtPlugin(jseditor, e) { + button: { + openModalAction: function openOeoExtPlugin(jseditor, e) { // Perform the HTMX request or any other desired action - htmx.ajax('GET', createUrl, { - target: '.modal-body', - swap: 'innerHTML', - trigger: 'click' + htmx.ajax("GET", createUrl, { + target: ".modal-body", + swap: "innerHTML", + trigger: "click", }); - $('#formModal').modal('show'); - } - } + $("#formModal").modal("show"); + }, + }, }; }); } @@ -491,3 +562,6 @@ var MetaEdit = function(config) { return config; }; + +/* notify inline code that MetaEdit is loaded */ +window.dispatchEvent(new Event("MetaEdit:ready")); diff --git a/dataedit/static/wizard/wizard.js b/dataedit/static/wizard/wizard.js index 56d039802..fd1c386e8 100644 --- a/dataedit/static/wizard/wizard.js +++ b/dataedit/static/wizard/wizard.js @@ -1,11 +1,14 @@ -// SPDX-FileCopyrightText: 2025 Christian Winger © Öko-Institut e.V. -// SPDX-FileCopyrightText: 2025 Eike Broda -// SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut -// SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut -// SPDX-FileCopyrightText: 2025 Christian Winger © Öko-Institut e.V. -// SPDX-FileCopyrightText: 2025 user © Reiner Lemoine Institut -// -// SPDX-License-Identifier: AGPL-3.0-or-later +/* eslint-disable max-len */ +/* +SPDX-FileCopyrightText: 2025 Christian Winger © Öko-Institut e.V. +SPDX-FileCopyrightText: 2025 Eike Broda +SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut +SPDX-FileCopyrightText: 2025 Jonas Huber © Reiner Lemoine Institut +SPDX-FileCopyrightText: 2025 Christian Winger © Öko-Institut e.V. +SPDX-FileCopyrightText: 2025 user © Reiner Lemoine Institut +SPDX-License-Identifier: AGPL-3.0-or-later +*/ +/* eslint-enable max-len */ window.Wizard = function (config) { var state = { @@ -122,7 +125,7 @@ window.Wizard = function (config) { * add a new column in the create table section */ function addColumn(columnDef) { - columnDef = columnDef || {}; + columnDef = columnDef || { is_nullable: true }; var columns = $("#wizard-columns"); var n = columns.find(".wizard-column").length; var column = $("#wizard-column-template") @@ -172,7 +175,6 @@ window.Wizard = function (config) { * add a new column in the csv upload section */ function addColumnCsv(columnDef) { - // console.log("add column csv", columnDef) columnDef = columnDef || {}; var columns = $("#wizard-csv-columns"); var n = columns.find(".wizard-csv-column").length; @@ -375,7 +377,6 @@ window.Wizard = function (config) { * Update the upload preview table */ function updatePreview() { - // console.log('updatePreview', state) var tbody = $("#wizard-csv-preview").find("tbody"); tbody.empty(); var rows = state.previewRows.length @@ -397,6 +398,11 @@ window.Wizard = function (config) { * The selected file has changed */ function updateFile() { + /* reset */ + state.previewRows = []; + updateExample(); + updatePreview(); + // console.log('updateFile') state.file = $("#wizard-file"); state.encoding = $("#wizard-encoding").find(":selected").val(); @@ -434,7 +440,6 @@ window.Wizard = function (config) { * Update the example data */ function updateExample() { - // console.log('updateExample') var exampleText = ""; if (state.columns) { var delim = state.delimiter || ","; @@ -462,14 +467,17 @@ window.Wizard = function (config) { * a file settings option (e.g. delimiter, encoding, ...) has been changed */ function changeFileSettings() { - // console.log('changeFileSettings') + $("#wizard-table-upload").hide(); + updateFile(); + state.csvColumns = []; state.previewRows = []; updateExample(); $("#wizard-csv-columns").empty(); $("#wizard-csv-text").text(""); if (state.file) { + setStatusUpload("info", false, "checking file", true); state.file.parse({ config: { encoding: state.encoding, @@ -516,9 +524,23 @@ window.Wizard = function (config) { } } updateColumnMapping(); + $("#wizard-table-upload").show(); + setStatusUpload( + "info", + false, + "", + false + ); /* remove status message*/ }, error: function (error) { - setStatusUpload("danger", false, error, false); + var msg = error.message; + if (msg == "File could not be read") { + msg = + "File could not be read " + + "(Probably too large - should be smaller than 1 GB)"; + } + console.error(msg); + setStatusUpload("danger", false, msg, false); }, }, }); @@ -587,8 +609,9 @@ window.Wizard = function (config) { } /** * - * NOTE: the api returns Bigints as connection/cursor ids, and the normal JSON.parse truncates those - * so we need to parse those manually to extract the id and keep it as string + * NOTE: the api returns Bigints as connection/cursor ids, and the normal JSON.parse + * truncates those so we need to parse those manually to extract the id + * and keep it as string */ function getJSONBigintKey(key, str) { var pat = new RegExp('"' + key + '":[ ]*([0-9]+)'); @@ -602,11 +625,6 @@ window.Wizard = function (config) { * First we open a new advanced connection and cursor * We read the csv in chunks (set size in state variable) * on each chunk we pause and and post the data. on success we resume the csv parser - * - , - - - * */ function csvUpload() { Promise.all([ @@ -669,7 +687,10 @@ window.Wizard = function (config) { data.data = data.data.slice(1); } if (data.data.length > 0) { - // if chunk size is too small, you can get a chunk with 0 complete rows, but the database does not allow empty insert + /* + if chunk size is too small, you can get a chunk with 0 complete + rows, but the database does not allow empty insert + */ state.csvParser = parser; // pause the csv parser state.csvParser.pause(); @@ -714,7 +735,6 @@ window.Wizard = function (config) { return sendJson("POST", urlConClose, createContext()); }) .then(function () { - // setStatusUpload("success", false, "Upload ok: " + state.uploadedRows + " rows", false); resetUpload(); // reset or reload page setStatusUpload( "success", @@ -768,7 +788,8 @@ window.Wizard = function (config) { var data = { query: { columns: colDefs, - embargo: embargoValue === "none" ? null : { duration: embargoValue }, // Conditional check + // Conditional check + embargo: embargoValue === "none" ? null : { duration: embargoValue }, // "embargo": embargoData }, }; @@ -776,7 +797,7 @@ window.Wizard = function (config) { Promise.all([ window.reverseUrl("api:api_table", { table: tablename }), window.reverseUrl("dataedit:wizard_upload", { - table: tablename, + table: tablename, }), ]).then(([urlTable, urlSuccess]) => { sendJson("PUT", urlTable, JSON.stringify(data)) @@ -816,32 +837,9 @@ window.Wizard = function (config) { }; } - /** - * delete table - */ - function deleteTable() { - $("#wizard-confirm-delete").modal("hide"); - setStatusCreate("primary", true, "deleting table..."); - var tablename = $("#wizard-tablename").val(); - - Promise.all([ - window.reverseUrl("api:api_table", { table: tablename }), - window.reverseUrl("dataedit:wizard_create"), - ]).then(([urlTable, urlSuccess]) => { - sendJson("DELETE", urlTable) - .then(function () { - setStatusCreate("success", true, "ok, reloading page..."); - window.location = urlSuccess; - }) - .catch(function (err) { - setStatusCreate("danger", false, getErrorMsg(err)); - }); - }); - } - function resetUpload() { state.cancel = null; - $("#wizard-table-upload").show(); + $("#wizard-table-upload").hide(); $("#wizard-table-upload-cancel").hide(); $("#wizard-file").val(""); changeFileSettings(); @@ -854,25 +852,22 @@ window.Wizard = function (config) { data_type: "bigserial", is_nullable: false, }); + new bootstrap.Collapse("#wizard-container-create", { toggle: false, }).show(); new bootstrap.Collapse("#wizard-container-upload", { toggle: false, }).hide(); - $("#wizard-table-delete").hide(); + $("#wizard-container-upload").find(".btn").hide(); $("#wizard-container-upload").find("input").prop("readonly", true); } function showUpload() { - new bootstrap.Collapse("#wizard-container-create", { - toggle: false, - }).hide(); new bootstrap.Collapse("#wizard-container-upload", { toggle: false, }).show(); - $("#wizard-table-delete").show(); $("#wizard-container-create").find(".btn").hide(); $("#wizard-container-create").find("input").prop("readonly", true); @@ -931,7 +926,6 @@ window.Wizard = function (config) { } (function init() { - // console.log('init') var cParseDiv = $("#wizard-csv-column-template .wizard-csv-column-parse"); Object.keys(columnParsers).map(function (k) { cParseDiv.append( @@ -956,12 +950,6 @@ window.Wizard = function (config) { tgt.addClass("is-invalid"); } }); - // Add this block to remove the "Create Table" card if canAdd is true - if (state.canAdd) { - // Remove the "Create Table" card - $("#wizard-container-create").closest(".card").remove(); - } - resetUpload(); if (state.table) { $("#wizard-tablename").val(state.table); @@ -977,15 +965,6 @@ window.Wizard = function (config) { cN.append(""); }); - /* delete table */ - $("#wizard-table-delete").bind("click", function () { - $("#wizard-confirm-delete").modal("show"); - }); - $("#wizard-confirm-delete-cancel").bind("click", function () { - $("#wizard-confirm-delete").modal("hide"); - }); - $("#wizard-confirm-delete-delete").bind("click", deleteTable); - showUpload(); } else { showCreate(); diff --git a/dataedit/templates/dataedit/base_fullwidth.html b/dataedit/templates/dataedit/base_fullwidth.html index 639c2ddb1..ba073115b 100644 --- a/dataedit/templates/dataedit/base_fullwidth.html +++ b/dataedit/templates/dataedit/base_fullwidth.html @@ -7,6 +7,9 @@ {% block main-top-bar-filter %}{% endblock %} {% block data_content %}{% endblock %} {% endblock %} +{% block before-body-bottom-js %} + {% include 'base/reverseUrl.html' %} +{% endblock before-body-bottom-js %} {% block after-body-bottom-js %} {% endblock after-body-bottom-js %} diff --git a/dataedit/templates/dataedit/dataedit_tablelist.html b/dataedit/templates/dataedit/dataedit_tablelist.html index 897b75122..8c74253ee 100644 --- a/dataedit/templates/dataedit/dataedit_tablelist.html +++ b/dataedit/templates/dataedit/dataedit_tablelist.html @@ -59,20 +59,21 @@

    {% if tables_paginated.has_previous %}
  • Previous
  • {% endif %}
  • - + {{ tables_paginated.number }}
  • {% if tables_paginated.has_next %}
  • Next + href="?{{ params_wo_page }}&page={{ tables_paginated.next_page_number }}">Next
  • {% endif %} diff --git a/dataedit/templates/dataedit/dataview.html b/dataedit/templates/dataedit/dataview.html index e6c3a3c88..bdf263ffc 100644 --- a/dataedit/templates/dataedit/dataview.html +++ b/dataedit/templates/dataedit/dataview.html @@ -652,7 +652,7 @@

    API Usage

    Do you want to delete the table (including data and metadata)? - -
    -
    -
    - - -
    -
    -
    - - + {% endblock main %} {% block after-body-bottom-js %} {% compress js %} - {% endcompress %} + {% vite_asset "dataedit/static/metaedit/metaedit.js" %} {% endblock after-body-bottom-js %} diff --git a/dataedit/templates/dataedit/taggable_setting.html b/dataedit/templates/dataedit/taggable_setting.html index 544129be1..e2462e3d6 100644 --- a/dataedit/templates/dataedit/taggable_setting.html +++ b/dataedit/templates/dataedit/taggable_setting.html @@ -9,34 +9,36 @@ style="background:{{ t.color_hex }}; color:{% readable_text_color t.color_hex %}">{{ t.name }} {% endfor %} - -
    -
    - {% csrf_token %} -

    Choose tags to attach:

    - {% for t in all_tags %} - - - - - {% endfor %} - {% if table %} - - {% endif %} - - Edit - -
    + {% if is_admin %} + +
    +
    + {% csrf_token %} +

    Choose tags to attach:

    + {% for t in all_tags %} + + + + + {% endfor %} + {% if table %} + + {% endif %} + + Edit + +
    + {% endif %}
    diff --git a/dataedit/templates/dataedit/wizard.html b/dataedit/templates/dataedit/wizard.html index 76a662186..158888bf7 100644 --- a/dataedit/templates/dataedit/wizard.html +++ b/dataedit/templates/dataedit/wizard.html @@ -1,59 +1,53 @@ -{% extends "dataedit/base.html" %} +{% extends "dataedit/base_fullwidth.html" %} {% load static %} +{% load django_vite %} {% load django_bootstrap5 %} {% load compress %} {% block after-head %} {% endblock after-head %} {% block site-header %} - -{% endblock site-header %} -{% block main %}
    -

    - - - - Data Table Wizard -

    -
    - {% if table %} - Edit Table : + {% if table %} +

    + Upload data from csv +

    +
    + Tables / {% if table_label %} {{ table_label }} {% else %} {{ table }} {% endif %} - {% else %} - Topics / Wizard - {% endif %} - -
    -
    -
    - In the academy you can learn about how to - create database conform CSV data - and how to work with the - wizard. -
    + +
    +
    + In the academy you can learn about how to + create database conform CSV data + and how to work with the + upload wizard. +
    + {% else %} +

    + Create table +

    +
    +
    + In the academy you can learn about how to + how to work create new tables using the wizard. +
    + {% endif %}
    +{% endblock site-header %} +{% block data_content %} -
    -
    -
    -
    -

    - Create Table -

    +
    +
    +
    +
    +
    placeholder="insert table name" id="wizard-tablename" data-bs-toggle="tooltip" - title="Valid table name (only lower case letters, numbers and underscore, max. 50 chars)" /> + title="Valid table name (only lower case letters, numbers and underscore, max. 50 chars, must start with a character)" />
    @@ -93,7 +87,7 @@

    @@ -123,7 +117,7 @@

    class="form-control wizard-column-name is-invalid" placeholder="" data-bs-toggle="tooltip" - title="Valid column name (only lower case letters, numbers and underscore, max. 50 chars)" /> + title="Valid column name (only lower case letters, numbers and underscore, max. 50 chars, must start with a character)" />

    + title="Valid column name (only lower case letters, numbers and underscore, max. 50 chars, must start with a character)"> Column name
    -
    -
    - -
    +
    +
    -
    -
    - - -
    +
    + +
    -
    -
    - - -
    -
    +
    + +
    +
    +
    -
    -
    -
    -

    - Upload CSV - -

    -
    @@ -348,16 +325,15 @@

    Preview and upload

    - + - +
    - + Upload @@ -379,30 +355,10 @@

    Preview and upload

    -
    -
    -

    - - Edit metadata -

    -
    -
    -
    -
    -

    - - View data -

    -
    -
    -{% endblock main %} +{% endblock data_content %} {% block after-body-bottom-js %}
    - {% compress js %} - - {% endcompress %} + {% vite_asset "dataedit/static/wizard/wizard.js" %}