From 3296e854d7248ce4946e8725f5da0c3e8c4115c4 Mon Sep 17 00:00:00 2001 From: alesan99 Date: Wed, 2 Oct 2024 09:34:53 -0500 Subject: [PATCH 1/5] WIP kml export using json --- .../lib/components/QueryBuilder/Export.tsx | 154 +++++++++++++++++- 1 file changed, 146 insertions(+), 8 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/QueryBuilder/Export.tsx b/specifyweb/frontend/js_src/lib/components/QueryBuilder/Export.tsx index 01f16e8d571..b4fcdfa5d55 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryBuilder/Export.tsx +++ b/specifyweb/frontend/js_src/lib/components/QueryBuilder/Export.tsx @@ -22,6 +22,8 @@ import { QueryButton } from './Components'; import type { QueryField } from './helpers'; import { hasLocalityColumns } from './helpers'; import type { QueryResultRow } from './Results'; +import { format } from '../Formatters/formatters'; +import { jsonToXml } from '../Syncer/xmlToJson'; export function QueryExportButtons({ baseTableName, @@ -89,16 +91,23 @@ export function QueryExportButtons({ 'exportCsvUtf8Bom' ); + function formatExportFileName( + file_extension: string + ): string { + return `${ + queryResource.isNew() + ? `${queryText.newQueryName()} ${genericTables[baseTableName].label}` + : queryResource.get('name') + } - ${new Date().toDateString()}.${file_extension}`; + } + /* *Will be only called if query is not distinct, *selection not enabled when distinct selected */ - async function exportSelected(): Promise { - const name = `${ - queryResource.isNew() - ? `${queryText.newQueryName()} ${genericTables[baseTableName].label}` - : queryResource.get('name') - } - ${new Date().toDateString()}.csv`; + + async function exportCsvSelected(): Promise { + const name = formatExportFileName('csv'); const selectedResults = results?.current?.map((row) => row !== undefined && f.has(selectedRows, row[0]) @@ -125,6 +134,131 @@ export function QueryExportButtons({ ); } + async function exportKmlSelected(): Promise { + const name = formatExportFileName('kml'); + + const selectedResults = results?.current?.map((row) => + row !== undefined && f.has(selectedRows, row[0]) + ? row?.slice(1).map((cell) => cell?.toString() ?? '') + : undefined + ); + + if (selectedResults === undefined) return undefined; + + let jsonData: { + tagName: string; + attributes: { + xmlns: string; + }; + children: RA<{ + tagName: string; + attributes: {}; + children: RA<{}> + }>; + } = { + tagName: "kml", + attributes: { + xmlns: "http://earth.google.com/kml/2.2" + }, + children: [ + { + tagName: "Document", + attributes: {}, + children: [] + } + ] + }; + + let placemarkTarget = jsonData.children[0].children; + + selectedResults?.forEach((result) => { + let placemark = { + tagName: "Placemark", + attributes: {}, + children: [] + }; + + // + let extendedData: { + tagName: string; + attributes: {}; + children: Array<{ + tagName: string; + attributes: { name: string }; + children: Array<{ + tagName: string; + attributes: {}; + children: string[] + }> + }> + } = { + tagName: "ExtendedData", + attributes: {}, + children: [] + }; + + fields.forEach((field, index) => { + const fieldValue = result?.[index + 1]; + + (extendedData.children as Array<{ + tagName: string; + attributes: { name: string }; + children: Array<{ tagName: string; attributes: {}; children: string[] }>; + }>).push({ + tagName: "Data", + attributes: { name: field.mappingPath.toString() }, + children: [ + { + tagName: "value", + attributes: {}, + children: [String(fieldValue)] + } + ] + }); + }); + // push + placemark.children.push(extendedData); + + // + const nameValue = fields.map((field) => result?.[field.id]).join(' - '); + let nameData = { + tagName: "name", + attributes: {}, + children: [nameValue] + }; + // push + placemark.children.push(nameData); + + // + const coordinatesValue = fields + .filter( + (field) => + field.mappingPath.toString().includes('latitude') || + field.mappingPath.toString().includes('longitude') + ) + .map((field) => result?.[field.id]) + .join(', '); + let pointData = { + tagName: "Point", + attributes: {}, + children: [ + { + tagName: "coordinates", + attributes: {}, + children: [coordinatesValue] + } + ] + }; + // push + placemark.children.push(pointData); + }); + + const json = JSON.stringify(jsonData); + const fileData = jsonToXml(json); + + return downloadFile(name, fileData); + } + const containsResults = results.current?.some((row) => row !== undefined); const canUseKml = @@ -159,7 +293,7 @@ export function QueryExportButtons({ onClick={(): void => { selectedRows.size === 0 ? doQueryExport('/stored_query/exportcsv/', separator, utf8Bom) - : exportSelected().catch(softFail); + : exportCsvSelected().catch(softFail); }} > {queryText.createCsv()} @@ -171,7 +305,11 @@ export function QueryExportButtons({ showConfirmation={showConfirmation} onClick={(): void => hasLocalityColumns(fields) - ? doQueryExport('/stored_query/exportkml/', undefined, undefined) + ? ( + selectedRows.size === 0 + ? doQueryExport('/stored_query/exportkml/', undefined, undefined) + : exportKmlSelected().catch(softFail) + ) : setState('warning') } > From 62a3e6f248ee69306c2873d18da96d12240c65ab Mon Sep 17 00:00:00 2001 From: alesan99 Date: Wed, 2 Oct 2024 09:51:05 -0500 Subject: [PATCH 2/5] WIP start fixing types --- .../lib/components/QueryBuilder/Export.tsx | 53 +++++++++++++++---- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/QueryBuilder/Export.tsx b/specifyweb/frontend/js_src/lib/components/QueryBuilder/Export.tsx index b4fcdfa5d55..98371467a00 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryBuilder/Export.tsx +++ b/specifyweb/frontend/js_src/lib/components/QueryBuilder/Export.tsx @@ -145,17 +145,51 @@ export function QueryExportButtons({ if (selectedResults === undefined) return undefined; - let jsonData: { + interface XmlNode { tagName: string; + attributes: { [key: string]: string | undefined }; + children: Array; + } + + interface XmlTextNode { + type: 'Text'; + string: string; + } + + interface XmlCommentNode { + type: 'Comment'; + comment: string; + } + + interface KmlData { + tagName: "kml"; attributes: { - xmlns: string; + xmlns: "http://earth.google.com/kml/2.2"; }; - children: RA<{ - tagName: string; - attributes: {}; - children: RA<{}> - }>; - } = { + children: XmlNode[]; + } + + interface Placemark extends XmlNode { + tagName: "Placemark"; + attributes: {}; + children: XmlNode[]; + } + + interface ExtendedData extends XmlNode { + tagName: "ExtendedData"; + attributes: {}; + children: XmlNode[]; + } + + interface Data extends XmlNode { + tagName: "Data"; + attributes: { + name: string; + }; + children: XmlTextNode[]; + } + + let jsonData: kmlData = { tagName: "kml", attributes: { xmlns: "http://earth.google.com/kml/2.2" @@ -172,7 +206,7 @@ export function QueryExportButtons({ let placemarkTarget = jsonData.children[0].children; selectedResults?.forEach((result) => { - let placemark = { + let placemark: Placemark = { tagName: "Placemark", attributes: {}, children: [] @@ -238,6 +272,7 @@ export function QueryExportButtons({ ) .map((field) => result?.[field.id]) .join(', '); + let pointData = { tagName: "Point", attributes: {}, From d5e518e1e4370728ac98fbcb212821004053355e Mon Sep 17 00:00:00 2001 From: alesan99 Date: Thu, 3 Oct 2024 10:49:07 -0500 Subject: [PATCH 3/5] Add functional kml exports Fix type errors (TODO: use XmlNode) --- .../lib/components/QueryBuilder/Export.tsx | 115 +++++++----------- .../js_src/lib/components/Syncer/xmlToJson.ts | 2 +- 2 files changed, 44 insertions(+), 73 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/QueryBuilder/Export.tsx b/specifyweb/frontend/js_src/lib/components/QueryBuilder/Export.tsx index 98371467a00..ed4fe1359ba 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryBuilder/Export.tsx +++ b/specifyweb/frontend/js_src/lib/components/QueryBuilder/Export.tsx @@ -23,7 +23,8 @@ import type { QueryField } from './helpers'; import { hasLocalityColumns } from './helpers'; import type { QueryResultRow } from './Results'; import { format } from '../Formatters/formatters'; -import { jsonToXml } from '../Syncer/xmlToJson'; +import { jsonToXml, XmlNode } from '../Syncer/xmlToJson'; +import { downloadFile } from '../Molecules/FilePicker'; export function QueryExportButtons({ baseTableName, @@ -145,51 +146,15 @@ export function QueryExportButtons({ if (selectedResults === undefined) return undefined; - interface XmlNode { - tagName: string; - attributes: { [key: string]: string | undefined }; - children: Array; - } - - interface XmlTextNode { - type: 'Text'; - string: string; - } - - interface XmlCommentNode { - type: 'Comment'; - comment: string; - } - - interface KmlData { - tagName: "kml"; - attributes: { - xmlns: "http://earth.google.com/kml/2.2"; - }; - children: XmlNode[]; - } + const filteredResults = filterArray(selectedResults); - interface Placemark extends XmlNode { - tagName: "Placemark"; - attributes: {}; - children: XmlNode[]; - } - - interface ExtendedData extends XmlNode { - tagName: "ExtendedData"; - attributes: {}; - children: XmlNode[]; - } - - interface Data extends XmlNode { - tagName: "Data"; - attributes: { - name: string; - }; - children: XmlTextNode[]; - } + const columnsName = fields + .filter((field) => field.isDisplay) + .map((field) => + generateMappingPathPreview(baseTableName, field.mappingPath) + ); - let jsonData: kmlData = { + let jsonData: any = { tagName: "kml", attributes: { xmlns: "http://earth.google.com/kml/2.2" @@ -205,27 +170,15 @@ export function QueryExportButtons({ let placemarkTarget = jsonData.children[0].children; - selectedResults?.forEach((result) => { - let placemark: Placemark = { + filteredResults?.forEach((result: any) => { + let placemark: any = { tagName: "Placemark", attributes: {}, children: [] }; // - let extendedData: { - tagName: string; - attributes: {}; - children: Array<{ - tagName: string; - attributes: { name: string }; - children: Array<{ - tagName: string; - attributes: {}; - children: string[] - }> - }> - } = { + let extendedData: any = { tagName: "ExtendedData", attributes: {}, children: [] @@ -234,18 +187,16 @@ export function QueryExportButtons({ fields.forEach((field, index) => { const fieldValue = result?.[index + 1]; - (extendedData.children as Array<{ - tagName: string; - attributes: { name: string }; - children: Array<{ tagName: string; attributes: {}; children: string[] }>; - }>).push({ + extendedData.children.push({ tagName: "Data", attributes: { name: field.mappingPath.toString() }, children: [ { tagName: "value", attributes: {}, - children: [String(fieldValue)] + children: [], + type: "Text", + string: String(fieldValue) } ] }); @@ -255,10 +206,18 @@ export function QueryExportButtons({ // const nameValue = fields.map((field) => result?.[field.id]).join(' - '); - let nameData = { + let nameData: any = { tagName: "name", attributes: {}, - children: [nameValue] + children: [ + { + tagName: "name", + attributes: {}, + children: [], + type: "Text", + string: nameValue + } + ], }; // push placemark.children.push(nameData); @@ -273,25 +232,37 @@ export function QueryExportButtons({ .map((field) => result?.[field.id]) .join(', '); - let pointData = { + let pointData: any = { tagName: "Point", attributes: {}, children: [ { tagName: "coordinates", attributes: {}, - children: [coordinatesValue] + children: [ + { + tagName: "coordinates", + attributes: {}, + children: [], + type: "Text", + string: coordinatesValue + } + ], } ] }; // push placemark.children.push(pointData); + + // Insert placemark into document (target) + placemarkTarget.push(placemark); }); - const json = JSON.stringify(jsonData); - const fileData = jsonToXml(json); + const xmlElement = jsonToXml(jsonData); + const serializer = new XMLSerializer(); + const xmlString = serializer.serializeToString(xmlElement); - return downloadFile(name, fileData); + return downloadFile(name, xmlString); } const containsResults = results.current?.some((row) => row !== undefined); diff --git a/specifyweb/frontend/js_src/lib/components/Syncer/xmlToJson.ts b/specifyweb/frontend/js_src/lib/components/Syncer/xmlToJson.ts index 8227aeb89e3..68aee67a59d 100644 --- a/specifyweb/frontend/js_src/lib/components/Syncer/xmlToJson.ts +++ b/specifyweb/frontend/js_src/lib/components/Syncer/xmlToJson.ts @@ -50,7 +50,7 @@ export const xmlToJson = (element: Element): XmlNode => ({ /** * Reverse conversion to JSON */ -export function jsonToXml(node: XmlNode): Element { +export function jsonToXml(node: any): Element { const xmlDocument = document.implementation.createDocument(null, null); const element = xmlDocument.createElement(node.tagName); xmlDocument.append(element); From e0c788576253c51f8fa7c0f0fb2600b3081804a9 Mon Sep 17 00:00:00 2001 From: alesan99 Date: Fri, 4 Oct 2024 09:56:05 -0500 Subject: [PATCH 4/5] Fix incorrectly labeled columns Fix data format Add xml header Attempt to fix types --- .../lib/components/QueryBuilder/Export.tsx | 84 +++++++++++-------- 1 file changed, 48 insertions(+), 36 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/QueryBuilder/Export.tsx b/specifyweb/frontend/js_src/lib/components/QueryBuilder/Export.tsx index ed4fe1359ba..dc41440d52b 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryBuilder/Export.tsx +++ b/specifyweb/frontend/js_src/lib/components/QueryBuilder/Export.tsx @@ -23,7 +23,7 @@ import type { QueryField } from './helpers'; import { hasLocalityColumns } from './helpers'; import type { QueryResultRow } from './Results'; import { format } from '../Formatters/formatters'; -import { jsonToXml, XmlNode } from '../Syncer/xmlToJson'; +import { jsonToXml, XmlNode, SimpleXmlNode } from '../Syncer/xmlToJson'; import { downloadFile } from '../Molecules/FilePicker'; export function QueryExportButtons({ @@ -154,59 +154,51 @@ export function QueryExportButtons({ generateMappingPathPreview(baseTableName, field.mappingPath) ); - let jsonData: any = { - tagName: "kml", - attributes: { - xmlns: "http://earth.google.com/kml/2.2" - }, - children: [ - { - tagName: "Document", - attributes: {}, - children: [] - } - ] - }; - let placemarkTarget = jsonData.children[0].children; + let placemarkTarget: any = []; filteredResults?.forEach((result: any) => { - let placemark: any = { - tagName: "Placemark", - attributes: {}, - children: [] - }; + let dataTarget: any = []; // - let extendedData: any = { - tagName: "ExtendedData", - attributes: {}, - children: [] - }; + let extendedDataTarget: any = []; fields.forEach((field, index) => { const fieldValue = result?.[index + 1]; - extendedData.children.push({ + extendedDataTarget.push({ tagName: "Data", - attributes: { name: field.mappingPath.toString() }, + attributes: { name: columnsName[index + 1] }, children: [ { tagName: "value", attributes: {}, - children: [], - type: "Text", - string: String(fieldValue) + children: [ + { + tagName: "value", + attributes: {}, + children: [], + type: "Text", + string: String(fieldValue) + } + ], } ] }); }); + + let extendedData: XmlNode = { + tagName: "ExtendedData", + attributes: {}, + children: extendedDataTarget as ReadonlyArray + }; + // push - placemark.children.push(extendedData); + dataTarget.push(extendedData); // const nameValue = fields.map((field) => result?.[field.id]).join(' - '); - let nameData: any = { + let nameData: XmlNode = { tagName: "name", attributes: {}, children: [ @@ -220,7 +212,7 @@ export function QueryExportButtons({ ], }; // push - placemark.children.push(nameData); + dataTarget.push(nameData); // const coordinatesValue = fields @@ -232,7 +224,7 @@ export function QueryExportButtons({ .map((field) => result?.[field.id]) .join(', '); - let pointData: any = { + let pointData: XmlNode = { tagName: "Point", attributes: {}, children: [ @@ -252,15 +244,35 @@ export function QueryExportButtons({ ] }; // push - placemark.children.push(pointData); + dataTarget.push(pointData); + + let placemark: XmlNode = { + tagName: "Placemark", + attributes: {}, + children: dataTarget as ReadonlyArray + }; // Insert placemark into document (target) placemarkTarget.push(placemark); }); + let jsonData: XmlNode = { + tagName: "kml", + attributes: { + xmlns: "http://earth.google.com/kml/2.2" + }, + children: [ + { + tagName: "Document", + attributes: {}, + children: placemarkTarget as ReadonlyArray + } + ] + }; + const xmlElement = jsonToXml(jsonData); const serializer = new XMLSerializer(); - const xmlString = serializer.serializeToString(xmlElement); + const xmlString = '\n' + serializer.serializeToString(xmlElement); return downloadFile(name, xmlString); } From 4390fcdc96aca187716a4c9580236f886b680b81 Mon Sep 17 00:00:00 2001 From: alesan99 Date: Fri, 25 Oct 2024 09:58:52 -0500 Subject: [PATCH 5/5] WIP start adding functionality for backend to recieve set of selected records --- .../lib/components/QueryBuilder/Export.tsx | 140 +----------------- .../js_src/lib/components/Syncer/xmlToJson.ts | 2 +- specifyweb/stored_queries/execution.py | 12 +- 3 files changed, 18 insertions(+), 136 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/QueryBuilder/Export.tsx b/specifyweb/frontend/js_src/lib/components/QueryBuilder/Export.tsx index dc41440d52b..78341917cd4 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryBuilder/Export.tsx +++ b/specifyweb/frontend/js_src/lib/components/QueryBuilder/Export.tsx @@ -56,6 +56,7 @@ export function QueryExportButtons({ function doQueryExport( url: string, + selected: ReadonlySet | undefined, delimiter: string | undefined, bom: boolean | undefined ): void { @@ -73,6 +74,7 @@ export function QueryExportButtons({ generateMappingPathPreview(baseTableName, mappingPath) ), recordSetId, + selected, delimiter, bom, }), @@ -136,8 +138,6 @@ export function QueryExportButtons({ } async function exportKmlSelected(): Promise { - const name = formatExportFileName('kml'); - const selectedResults = results?.current?.map((row) => row !== undefined && f.has(selectedRows, row[0]) ? row?.slice(1).map((cell) => cell?.toString() ?? '') @@ -145,136 +145,10 @@ export function QueryExportButtons({ ); if (selectedResults === undefined) return undefined; + + doQueryExport('/stored_query/exportkml/', selectedRows, undefined, undefined) - const filteredResults = filterArray(selectedResults); - - const columnsName = fields - .filter((field) => field.isDisplay) - .map((field) => - generateMappingPathPreview(baseTableName, field.mappingPath) - ); - - - let placemarkTarget: any = []; - - filteredResults?.forEach((result: any) => { - let dataTarget: any = []; - - // - let extendedDataTarget: any = []; - - fields.forEach((field, index) => { - const fieldValue = result?.[index + 1]; - - extendedDataTarget.push({ - tagName: "Data", - attributes: { name: columnsName[index + 1] }, - children: [ - { - tagName: "value", - attributes: {}, - children: [ - { - tagName: "value", - attributes: {}, - children: [], - type: "Text", - string: String(fieldValue) - } - ], - } - ] - }); - }); - - let extendedData: XmlNode = { - tagName: "ExtendedData", - attributes: {}, - children: extendedDataTarget as ReadonlyArray - }; - - // push - dataTarget.push(extendedData); - - // - const nameValue = fields.map((field) => result?.[field.id]).join(' - '); - let nameData: XmlNode = { - tagName: "name", - attributes: {}, - children: [ - { - tagName: "name", - attributes: {}, - children: [], - type: "Text", - string: nameValue - } - ], - }; - // push - dataTarget.push(nameData); - - // - const coordinatesValue = fields - .filter( - (field) => - field.mappingPath.toString().includes('latitude') || - field.mappingPath.toString().includes('longitude') - ) - .map((field) => result?.[field.id]) - .join(', '); - - let pointData: XmlNode = { - tagName: "Point", - attributes: {}, - children: [ - { - tagName: "coordinates", - attributes: {}, - children: [ - { - tagName: "coordinates", - attributes: {}, - children: [], - type: "Text", - string: coordinatesValue - } - ], - } - ] - }; - // push - dataTarget.push(pointData); - - let placemark: XmlNode = { - tagName: "Placemark", - attributes: {}, - children: dataTarget as ReadonlyArray - }; - - // Insert placemark into document (target) - placemarkTarget.push(placemark); - }); - - let jsonData: XmlNode = { - tagName: "kml", - attributes: { - xmlns: "http://earth.google.com/kml/2.2" - }, - children: [ - { - tagName: "Document", - attributes: {}, - children: placemarkTarget as ReadonlyArray - } - ] - }; - - const xmlElement = jsonToXml(jsonData); - const serializer = new XMLSerializer(); - const xmlString = '\n' + serializer.serializeToString(xmlElement); - - return downloadFile(name, xmlString); + return; } const containsResults = results.current?.some((row) => row !== undefined); @@ -310,7 +184,7 @@ export function QueryExportButtons({ showConfirmation={showConfirmation} onClick={(): void => { selectedRows.size === 0 - ? doQueryExport('/stored_query/exportcsv/', separator, utf8Bom) + ? doQueryExport('/stored_query/exportcsv/', undefined, separator, utf8Bom) : exportCsvSelected().catch(softFail); }} > @@ -325,7 +199,7 @@ export function QueryExportButtons({ hasLocalityColumns(fields) ? ( selectedRows.size === 0 - ? doQueryExport('/stored_query/exportkml/', undefined, undefined) + ? doQueryExport('/stored_query/exportkml/', undefined, undefined, undefined) : exportKmlSelected().catch(softFail) ) : setState('warning') diff --git a/specifyweb/frontend/js_src/lib/components/Syncer/xmlToJson.ts b/specifyweb/frontend/js_src/lib/components/Syncer/xmlToJson.ts index 68aee67a59d..8227aeb89e3 100644 --- a/specifyweb/frontend/js_src/lib/components/Syncer/xmlToJson.ts +++ b/specifyweb/frontend/js_src/lib/components/Syncer/xmlToJson.ts @@ -50,7 +50,7 @@ export const xmlToJson = (element: Element): XmlNode => ({ /** * Reverse conversion to JSON */ -export function jsonToXml(node: any): Element { +export function jsonToXml(node: XmlNode): Element { const xmlDocument = document.implementation.createDocument(null, null); const element = xmlDocument.createElement(node.tagName); xmlDocument.append(element); diff --git a/specifyweb/stored_queries/execution.py b/specifyweb/stored_queries/execution.py index d2328e0e1a7..060c3b7fc53 100644 --- a/specifyweb/stored_queries/execution.py +++ b/specifyweb/stored_queries/execution.py @@ -161,7 +161,7 @@ def do_export(spquery, collection, user, filename, exporttype, host): distinct=spquery['selectdistinct'], delimiter=spquery['delimiter'], bom=spquery['bom']) elif exporttype == 'kml': query_to_kml(session, collection, user, tableid, field_specs, path, spquery['captions'], host, - recordsetid=recordsetid, strip_id=False) + recordsetid=recordsetid, strip_id=False, selected=spquery['selected']) message_type = 'query-export-to-kml-complete' Message.objects.create(user=user, content=json.dumps({ @@ -227,7 +227,7 @@ def row_has_geocoords(coord_cols, row): def query_to_kml(session, collection, user, tableid, field_specs, path, captions, host, - recordsetid=None, strip_id=False): + recordsetid=None, strip_id=False, selected=None): """Build a sqlalchemy query using the QueryField objects given by field_specs and send the results to a kml file at the given file path. @@ -236,6 +236,14 @@ def query_to_kml(session, collection, user, tableid, field_specs, path, captions """ set_group_concat_max_len(session.connection()) query, __ = build_query(session, collection, user, tableid, field_specs, recordsetid, replace_nulls=True) + logger.debug(f"selected: {selected}") + logger.debug(query) + if selected: + # TODO + # If selected is defined (its a set of numerical ids), modify the query to only include those ids + # the build_query function could also be modified to take a selected ids parameter, which can be used for batch edit + # query = query.filter(getattr(models.models_by_tableid[tableid], models.models_by_tableid[tableid]._id).in_(selected)) + pass logger.debug('query_to_kml starting')