diff --git a/config/common/leaflet-layers.json b/config/common/leaflet-layers.json
new file mode 100644
index 00000000000..970fb60eb63
--- /dev/null
+++ b/config/common/leaflet-layers.json
@@ -0,0 +1,118 @@
+{
+ "baseMaps": {
+ "Street Map (OpenStreetMap)": {
+ "endpoint": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
+ "serverType": "tileServer",
+ "layerOptions": {
+ "maxZoom": 19,
+ "attribution": "© OpenStreetMap contributors"
+ },
+ "styles": [
+ "auto-dark-mode"
+ ]
+ },
+ "Street Map (ESRI)": {
+ "endpoint": "https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}",
+ "serverType": "tileServer",
+ "layerOptions": {
+ "maxZoom": 23,
+ "attribution": "Esri, HERE, Garmin, USGS, Intermap, INCREMENT P, NRCan, Esri Japan, METI, Esri China (Hong Kong), Esri Korea, Esri (Thailand), NGCC, (c) OpenStreetMap contributors, and the GIS User Community"
+ },
+ "styles": [
+ "auto-dark-mode"
+ ]
+ },
+ "Topographic Map (ESRI)": {
+ "endpoint": "https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}",
+ "serverType": "tileServer",
+ "layerOptions": {
+ "maxZoom": 23,
+ "attribution": "Sources: Esri, HERE, Garmin, Intermap, increment P Corp., GEBCO, USGS, FAO, NPS, NRCAN, GeoBase, IGN, Kadaster NL, Ordnance Survey, Esri Japan, METI, Esri China (Hong Kong), (c) OpenStreetMap contributors, and the GIS User Community"
+ },
+ "styles": [
+ "auto-dark-mode"
+ ]
+ },
+ "Satellite Map (ESRI)": {
+ "endpoint": "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
+ "serverType": "tileServer",
+ "layerOptions": {
+ "maxZoom": 23,
+ "attribution": "Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community"
+ }
+ },
+ "Satellite Map (ESRI) (grayscale)": {
+ "endpoint": "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
+ "serverType": "tileServer",
+ "layerOptions": {
+ "maxZoom": 23,
+ "attribution": "Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community"
+ },
+ "styles": [
+ "grayscale"
+ ]
+ },
+ "Satellite Map (Géoportail/France)": {
+ "endpoint": "https://wxs.ign.fr/{apikey}/geoportail/wmts?REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&STYLE={style}&TILEMATRIXSET=PM&FORMAT={format}&LAYER=ORTHOIMAGERY.ORTHOPHOTOS&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}",
+ "serverType": "tileServer",
+ "layerOptions": {
+ "attribution": "Geoportail France",
+ "bounds": [
+ [
+ -75,
+ -180
+ ],
+ [
+ 81,
+ 180
+ ]
+ ],
+ "minZoom": 2,
+ "maxZoom": 12,
+ "apikey": "choisirgeoportail",
+ "format": "image/jpeg",
+ "style": "normal"
+ }
+ },
+ "Satellite Map (USGS)": {
+ "endpoint": "https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer/tile/{z}/{y}/{x}",
+ "serverType": "tileServer",
+ "layerOptions": {
+ "maxZoom": 16,
+ "attribution": "Tiles courtesy of the U.S. Geological Survey"
+ }
+ },
+ "Live Satellite Map (NASA/GIBS)": {
+ "endpoint": "https://gibs.earthdata.nasa.gov/wmts/epsg3031/best/MODIS_Terra_CorrectedReflectance_TrueColor/default/{time}/{tilematrixset}/{z}/{y}/{x}.{format}",
+ "serverType": "tileServer",
+ "layerOptions": {
+ "attribution": "Imagery provided by services from the Global Imagery Browse Services (GIBS), operated by the NASA/GSFC/Earth Science Data and Information System (ESDIS) with funding provided by NASA/HQ.",
+ "bounds": [
+ [
+ -85.0511287776,
+ -179.999999975
+ ],
+ [
+ 85.0511287776,
+ 179.999999975
+ ]
+ ],
+ "minZoom": 1,
+ "maxZoom": 9,
+ "format": "jpg",
+ "time": "",
+ "tilematrixset": "250m"
+ }
+ }
+ },
+ "overlays": {
+ "Labels and boundaries": {
+ "endpoint": "https://esp.usdoj.gov/arcweb/rest/services/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}",
+ "serverType": "tileServer",
+ "layerOptions": {
+ "maxZoom": 23,
+ "attribution": "Esri, HERE, Garmin, (c) OpenStreetMap contributors, and the GIS user community"
+ }
+ }
+ }
+}
diff --git a/config/default_trees/2026_02_03_IMA_minerals.csv.gz b/config/default_trees/2026_02_03_IMA_minerals.csv.gz
new file mode 100644
index 00000000000..607de4d6191
Binary files /dev/null and b/config/default_trees/2026_02_03_IMA_minerals.csv.gz differ
diff --git a/config/default_trees/GeologicTimePeriod.csv.gz b/config/default_trees/GeologicTimePeriod.csv.gz
new file mode 100644
index 00000000000..28bd1accc3c
Binary files /dev/null and b/config/default_trees/GeologicTimePeriod.csv.gz differ
diff --git a/config/default_trees/col2008_aves.csv.gz b/config/default_trees/col2008_aves.csv.gz
new file mode 100644
index 00000000000..f5fa5afc5da
Binary files /dev/null and b/config/default_trees/col2008_aves.csv.gz differ
diff --git a/config/default_trees/col2008_fishes.csv.gz b/config/default_trees/col2008_fishes.csv.gz
new file mode 100644
index 00000000000..8835537366b
Binary files /dev/null and b/config/default_trees/col2008_fishes.csv.gz differ
diff --git a/config/default_trees/col2008_herps.csv.gz b/config/default_trees/col2008_herps.csv.gz
new file mode 100644
index 00000000000..b42e47e7a9a
Binary files /dev/null and b/config/default_trees/col2008_herps.csv.gz differ
diff --git a/config/default_trees/col2008_inverts.csv.gz b/config/default_trees/col2008_inverts.csv.gz
new file mode 100644
index 00000000000..c7e05352edf
Binary files /dev/null and b/config/default_trees/col2008_inverts.csv.gz differ
diff --git a/config/default_trees/col2008_mammalia.csv.gz b/config/default_trees/col2008_mammalia.csv.gz
new file mode 100644
index 00000000000..08431d6a846
Binary files /dev/null and b/config/default_trees/col2008_mammalia.csv.gz differ
diff --git a/config/default_trees/col2008_orthoptera.csv.gz b/config/default_trees/col2008_orthoptera.csv.gz
new file mode 100644
index 00000000000..7999c27a720
Binary files /dev/null and b/config/default_trees/col2008_orthoptera.csv.gz differ
diff --git a/config/default_trees/col2008_poales.csv.gz b/config/default_trees/col2008_poales.csv.gz
new file mode 100644
index 00000000000..88040a59f7f
Binary files /dev/null and b/config/default_trees/col2008_poales.csv.gz differ
diff --git a/config/default_trees/col2021_actinopterygii.csv.gz b/config/default_trees/col2021_actinopterygii.csv.gz
new file mode 100644
index 00000000000..b9f30d1e9a5
Binary files /dev/null and b/config/default_trees/col2021_actinopterygii.csv.gz differ
diff --git a/config/default_trees/col2021_amphibia_reptilia.csv.gz b/config/default_trees/col2021_amphibia_reptilia.csv.gz
new file mode 100644
index 00000000000..df294c51096
Binary files /dev/null and b/config/default_trees/col2021_amphibia_reptilia.csv.gz differ
diff --git a/config/default_trees/col2021_aves.csv.gz b/config/default_trees/col2021_aves.csv.gz
new file mode 100644
index 00000000000..eea995ffbf8
Binary files /dev/null and b/config/default_trees/col2021_aves.csv.gz differ
diff --git a/config/default_trees/col2021_bryophyta.csv.gz b/config/default_trees/col2021_bryophyta.csv.gz
new file mode 100644
index 00000000000..71bf9b0158f
Binary files /dev/null and b/config/default_trees/col2021_bryophyta.csv.gz differ
diff --git a/config/default_trees/col2021_fungi.csv.gz b/config/default_trees/col2021_fungi.csv.gz
new file mode 100644
index 00000000000..b24802fa54f
Binary files /dev/null and b/config/default_trees/col2021_fungi.csv.gz differ
diff --git a/config/default_trees/col2021_mammalia.csv.gz b/config/default_trees/col2021_mammalia.csv.gz
new file mode 100644
index 00000000000..22f0266178e
Binary files /dev/null and b/config/default_trees/col2021_mammalia.csv.gz differ
diff --git a/config/default_trees/col2021_mollusca.csv.gz b/config/default_trees/col2021_mollusca.csv.gz
new file mode 100644
index 00000000000..cf190c1fa76
Binary files /dev/null and b/config/default_trees/col2021_mollusca.csv.gz differ
diff --git a/config/default_trees/col2021_mollusca.xlsx b/config/default_trees/col2021_mollusca.xlsx
new file mode 100644
index 00000000000..1feb38b3750
Binary files /dev/null and b/config/default_trees/col2021_mollusca.xlsx differ
diff --git a/config/default_trees/col2021_polypodiopsida.csv.gz b/config/default_trees/col2021_polypodiopsida.csv.gz
new file mode 100644
index 00000000000..73dcd96a733
Binary files /dev/null and b/config/default_trees/col2021_polypodiopsida.csv.gz differ
diff --git a/config/default_trees/col2021_tracheophyta.csv.gz b/config/default_trees/col2021_tracheophyta.csv.gz
new file mode 100644
index 00000000000..25dc15fd1ea
Binary files /dev/null and b/config/default_trees/col2021_tracheophyta.csv.gz differ
diff --git a/config/default_trees/geonames.csv.gz b/config/default_trees/geonames.csv.gz
new file mode 100644
index 00000000000..6abaf6c4aa2
Binary files /dev/null and b/config/default_trees/geonames.csv.gz differ
diff --git a/config/default_trees/mapping_files/2026_02_03_IMA_minerals.json b/config/default_trees/mapping_files/2026_02_03_IMA_minerals.json
new file mode 100644
index 00000000000..608f1b89844
--- /dev/null
+++ b/config/default_trees/mapping_files/2026_02_03_IMA_minerals.json
@@ -0,0 +1,21 @@
+{
+ "all_columns": ["Mineral Name","Mineral Name (plain)","Valence Chemistry","IMA Chemistry","IMA Number","Country of Type Locality","IMA Status","isMineral"],
+ "root": {
+ "name": "Root"
+ },
+ "ranks": [
+ {
+ "name": "Minerals",
+ "rank": 10,
+ "column": "Mineral Name",
+ "fields": {
+ "name": "Mineral Name (plain)",
+ "text10": "Valence Chemistry",
+ "text14": "IMA Chemistry",
+ "text12": "IMA Number",
+ "text13": "Country of Type Locality",
+ "text11": "IMA Status"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/config/default_trees/mapping_files/col2021_actinopterygii.json b/config/default_trees/mapping_files/col2021_actinopterygii.json
new file mode 100644
index 00000000000..7853736c746
--- /dev/null
+++ b/config/default_trees/mapping_files/col2021_actinopterygii.json
@@ -0,0 +1,94 @@
+{
+ "all_columns": [
+ "kingdom",
+ "phylum",
+ "class",
+ "order",
+ "family",
+ "genus",
+ "species",
+ "subspecies",
+ "family common name",
+ "species author",
+ "species source",
+ "species common name",
+ "subspecies author",
+ "subspecies source",
+ "subspecies common name"
+ ],
+ "ranks": [
+ {
+ "name": "Kingdom",
+ "rank": 10,
+ "column": "kingdom",
+ "fields": {
+ "name": "kingdom"
+ }
+ },
+ {
+ "name": "Phylum",
+ "rank": 30,
+ "column": "phylum",
+ "fields": {
+ "name": "phylum"
+ }
+ },
+ {
+ "name": "Class",
+ "rank": 60,
+ "column": "class",
+ "fields": {
+ "name": "class"
+ }
+ },
+ {
+ "name": "Order",
+ "rank": 100,
+ "column": "order",
+ "fields": {
+ "name": "order"
+ }
+ },
+ {
+ "name": "Family",
+ "rank": 140,
+ "column": "family",
+ "fields": {
+ "name": "family"
+ }
+ },
+ {
+ "name": "Genus",
+ "rank": 180,
+ "column": "genus",
+ "infullname": true,
+ "fields": {
+ "name": "genus"
+ }
+ },
+ {
+ "name": "Species",
+ "rank": 220,
+ "column": "species",
+ "infullname": true,
+ "fields": {
+ "name": "species",
+ "author": "species author",
+ "source": "species source",
+ "commonname": "species common name"
+ }
+ },
+ {
+ "name": "Subspecies",
+ "rank": 230,
+ "column": "subspecies",
+ "infullname": true,
+ "fields": {
+ "name": "subspecies",
+ "author": "subspecies author",
+ "source": "subspecies source",
+ "commonname": "subspecies common name"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/config/default_trees/mapping_files/col2021_amphibia_reptilia.json b/config/default_trees/mapping_files/col2021_amphibia_reptilia.json
new file mode 100644
index 00000000000..7853736c746
--- /dev/null
+++ b/config/default_trees/mapping_files/col2021_amphibia_reptilia.json
@@ -0,0 +1,94 @@
+{
+ "all_columns": [
+ "kingdom",
+ "phylum",
+ "class",
+ "order",
+ "family",
+ "genus",
+ "species",
+ "subspecies",
+ "family common name",
+ "species author",
+ "species source",
+ "species common name",
+ "subspecies author",
+ "subspecies source",
+ "subspecies common name"
+ ],
+ "ranks": [
+ {
+ "name": "Kingdom",
+ "rank": 10,
+ "column": "kingdom",
+ "fields": {
+ "name": "kingdom"
+ }
+ },
+ {
+ "name": "Phylum",
+ "rank": 30,
+ "column": "phylum",
+ "fields": {
+ "name": "phylum"
+ }
+ },
+ {
+ "name": "Class",
+ "rank": 60,
+ "column": "class",
+ "fields": {
+ "name": "class"
+ }
+ },
+ {
+ "name": "Order",
+ "rank": 100,
+ "column": "order",
+ "fields": {
+ "name": "order"
+ }
+ },
+ {
+ "name": "Family",
+ "rank": 140,
+ "column": "family",
+ "fields": {
+ "name": "family"
+ }
+ },
+ {
+ "name": "Genus",
+ "rank": 180,
+ "column": "genus",
+ "infullname": true,
+ "fields": {
+ "name": "genus"
+ }
+ },
+ {
+ "name": "Species",
+ "rank": 220,
+ "column": "species",
+ "infullname": true,
+ "fields": {
+ "name": "species",
+ "author": "species author",
+ "source": "species source",
+ "commonname": "species common name"
+ }
+ },
+ {
+ "name": "Subspecies",
+ "rank": 230,
+ "column": "subspecies",
+ "infullname": true,
+ "fields": {
+ "name": "subspecies",
+ "author": "subspecies author",
+ "source": "subspecies source",
+ "commonname": "subspecies common name"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/config/default_trees/mapping_files/col2021_aves.json b/config/default_trees/mapping_files/col2021_aves.json
new file mode 100644
index 00000000000..7853736c746
--- /dev/null
+++ b/config/default_trees/mapping_files/col2021_aves.json
@@ -0,0 +1,94 @@
+{
+ "all_columns": [
+ "kingdom",
+ "phylum",
+ "class",
+ "order",
+ "family",
+ "genus",
+ "species",
+ "subspecies",
+ "family common name",
+ "species author",
+ "species source",
+ "species common name",
+ "subspecies author",
+ "subspecies source",
+ "subspecies common name"
+ ],
+ "ranks": [
+ {
+ "name": "Kingdom",
+ "rank": 10,
+ "column": "kingdom",
+ "fields": {
+ "name": "kingdom"
+ }
+ },
+ {
+ "name": "Phylum",
+ "rank": 30,
+ "column": "phylum",
+ "fields": {
+ "name": "phylum"
+ }
+ },
+ {
+ "name": "Class",
+ "rank": 60,
+ "column": "class",
+ "fields": {
+ "name": "class"
+ }
+ },
+ {
+ "name": "Order",
+ "rank": 100,
+ "column": "order",
+ "fields": {
+ "name": "order"
+ }
+ },
+ {
+ "name": "Family",
+ "rank": 140,
+ "column": "family",
+ "fields": {
+ "name": "family"
+ }
+ },
+ {
+ "name": "Genus",
+ "rank": 180,
+ "column": "genus",
+ "infullname": true,
+ "fields": {
+ "name": "genus"
+ }
+ },
+ {
+ "name": "Species",
+ "rank": 220,
+ "column": "species",
+ "infullname": true,
+ "fields": {
+ "name": "species",
+ "author": "species author",
+ "source": "species source",
+ "commonname": "species common name"
+ }
+ },
+ {
+ "name": "Subspecies",
+ "rank": 230,
+ "column": "subspecies",
+ "infullname": true,
+ "fields": {
+ "name": "subspecies",
+ "author": "subspecies author",
+ "source": "subspecies source",
+ "commonname": "subspecies common name"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/config/default_trees/mapping_files/col2021_bryophyta.json b/config/default_trees/mapping_files/col2021_bryophyta.json
new file mode 100644
index 00000000000..7853736c746
--- /dev/null
+++ b/config/default_trees/mapping_files/col2021_bryophyta.json
@@ -0,0 +1,94 @@
+{
+ "all_columns": [
+ "kingdom",
+ "phylum",
+ "class",
+ "order",
+ "family",
+ "genus",
+ "species",
+ "subspecies",
+ "family common name",
+ "species author",
+ "species source",
+ "species common name",
+ "subspecies author",
+ "subspecies source",
+ "subspecies common name"
+ ],
+ "ranks": [
+ {
+ "name": "Kingdom",
+ "rank": 10,
+ "column": "kingdom",
+ "fields": {
+ "name": "kingdom"
+ }
+ },
+ {
+ "name": "Phylum",
+ "rank": 30,
+ "column": "phylum",
+ "fields": {
+ "name": "phylum"
+ }
+ },
+ {
+ "name": "Class",
+ "rank": 60,
+ "column": "class",
+ "fields": {
+ "name": "class"
+ }
+ },
+ {
+ "name": "Order",
+ "rank": 100,
+ "column": "order",
+ "fields": {
+ "name": "order"
+ }
+ },
+ {
+ "name": "Family",
+ "rank": 140,
+ "column": "family",
+ "fields": {
+ "name": "family"
+ }
+ },
+ {
+ "name": "Genus",
+ "rank": 180,
+ "column": "genus",
+ "infullname": true,
+ "fields": {
+ "name": "genus"
+ }
+ },
+ {
+ "name": "Species",
+ "rank": 220,
+ "column": "species",
+ "infullname": true,
+ "fields": {
+ "name": "species",
+ "author": "species author",
+ "source": "species source",
+ "commonname": "species common name"
+ }
+ },
+ {
+ "name": "Subspecies",
+ "rank": 230,
+ "column": "subspecies",
+ "infullname": true,
+ "fields": {
+ "name": "subspecies",
+ "author": "subspecies author",
+ "source": "subspecies source",
+ "commonname": "subspecies common name"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/config/default_trees/mapping_files/col2021_fungi.json b/config/default_trees/mapping_files/col2021_fungi.json
new file mode 100644
index 00000000000..7853736c746
--- /dev/null
+++ b/config/default_trees/mapping_files/col2021_fungi.json
@@ -0,0 +1,94 @@
+{
+ "all_columns": [
+ "kingdom",
+ "phylum",
+ "class",
+ "order",
+ "family",
+ "genus",
+ "species",
+ "subspecies",
+ "family common name",
+ "species author",
+ "species source",
+ "species common name",
+ "subspecies author",
+ "subspecies source",
+ "subspecies common name"
+ ],
+ "ranks": [
+ {
+ "name": "Kingdom",
+ "rank": 10,
+ "column": "kingdom",
+ "fields": {
+ "name": "kingdom"
+ }
+ },
+ {
+ "name": "Phylum",
+ "rank": 30,
+ "column": "phylum",
+ "fields": {
+ "name": "phylum"
+ }
+ },
+ {
+ "name": "Class",
+ "rank": 60,
+ "column": "class",
+ "fields": {
+ "name": "class"
+ }
+ },
+ {
+ "name": "Order",
+ "rank": 100,
+ "column": "order",
+ "fields": {
+ "name": "order"
+ }
+ },
+ {
+ "name": "Family",
+ "rank": 140,
+ "column": "family",
+ "fields": {
+ "name": "family"
+ }
+ },
+ {
+ "name": "Genus",
+ "rank": 180,
+ "column": "genus",
+ "infullname": true,
+ "fields": {
+ "name": "genus"
+ }
+ },
+ {
+ "name": "Species",
+ "rank": 220,
+ "column": "species",
+ "infullname": true,
+ "fields": {
+ "name": "species",
+ "author": "species author",
+ "source": "species source",
+ "commonname": "species common name"
+ }
+ },
+ {
+ "name": "Subspecies",
+ "rank": 230,
+ "column": "subspecies",
+ "infullname": true,
+ "fields": {
+ "name": "subspecies",
+ "author": "subspecies author",
+ "source": "subspecies source",
+ "commonname": "subspecies common name"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/config/default_trees/mapping_files/col2021_mammalia.json b/config/default_trees/mapping_files/col2021_mammalia.json
new file mode 100644
index 00000000000..7853736c746
--- /dev/null
+++ b/config/default_trees/mapping_files/col2021_mammalia.json
@@ -0,0 +1,94 @@
+{
+ "all_columns": [
+ "kingdom",
+ "phylum",
+ "class",
+ "order",
+ "family",
+ "genus",
+ "species",
+ "subspecies",
+ "family common name",
+ "species author",
+ "species source",
+ "species common name",
+ "subspecies author",
+ "subspecies source",
+ "subspecies common name"
+ ],
+ "ranks": [
+ {
+ "name": "Kingdom",
+ "rank": 10,
+ "column": "kingdom",
+ "fields": {
+ "name": "kingdom"
+ }
+ },
+ {
+ "name": "Phylum",
+ "rank": 30,
+ "column": "phylum",
+ "fields": {
+ "name": "phylum"
+ }
+ },
+ {
+ "name": "Class",
+ "rank": 60,
+ "column": "class",
+ "fields": {
+ "name": "class"
+ }
+ },
+ {
+ "name": "Order",
+ "rank": 100,
+ "column": "order",
+ "fields": {
+ "name": "order"
+ }
+ },
+ {
+ "name": "Family",
+ "rank": 140,
+ "column": "family",
+ "fields": {
+ "name": "family"
+ }
+ },
+ {
+ "name": "Genus",
+ "rank": 180,
+ "column": "genus",
+ "infullname": true,
+ "fields": {
+ "name": "genus"
+ }
+ },
+ {
+ "name": "Species",
+ "rank": 220,
+ "column": "species",
+ "infullname": true,
+ "fields": {
+ "name": "species",
+ "author": "species author",
+ "source": "species source",
+ "commonname": "species common name"
+ }
+ },
+ {
+ "name": "Subspecies",
+ "rank": 230,
+ "column": "subspecies",
+ "infullname": true,
+ "fields": {
+ "name": "subspecies",
+ "author": "subspecies author",
+ "source": "subspecies source",
+ "commonname": "subspecies common name"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/config/default_trees/mapping_files/col2021_mollusca.json b/config/default_trees/mapping_files/col2021_mollusca.json
new file mode 100644
index 00000000000..7853736c746
--- /dev/null
+++ b/config/default_trees/mapping_files/col2021_mollusca.json
@@ -0,0 +1,94 @@
+{
+ "all_columns": [
+ "kingdom",
+ "phylum",
+ "class",
+ "order",
+ "family",
+ "genus",
+ "species",
+ "subspecies",
+ "family common name",
+ "species author",
+ "species source",
+ "species common name",
+ "subspecies author",
+ "subspecies source",
+ "subspecies common name"
+ ],
+ "ranks": [
+ {
+ "name": "Kingdom",
+ "rank": 10,
+ "column": "kingdom",
+ "fields": {
+ "name": "kingdom"
+ }
+ },
+ {
+ "name": "Phylum",
+ "rank": 30,
+ "column": "phylum",
+ "fields": {
+ "name": "phylum"
+ }
+ },
+ {
+ "name": "Class",
+ "rank": 60,
+ "column": "class",
+ "fields": {
+ "name": "class"
+ }
+ },
+ {
+ "name": "Order",
+ "rank": 100,
+ "column": "order",
+ "fields": {
+ "name": "order"
+ }
+ },
+ {
+ "name": "Family",
+ "rank": 140,
+ "column": "family",
+ "fields": {
+ "name": "family"
+ }
+ },
+ {
+ "name": "Genus",
+ "rank": 180,
+ "column": "genus",
+ "infullname": true,
+ "fields": {
+ "name": "genus"
+ }
+ },
+ {
+ "name": "Species",
+ "rank": 220,
+ "column": "species",
+ "infullname": true,
+ "fields": {
+ "name": "species",
+ "author": "species author",
+ "source": "species source",
+ "commonname": "species common name"
+ }
+ },
+ {
+ "name": "Subspecies",
+ "rank": 230,
+ "column": "subspecies",
+ "infullname": true,
+ "fields": {
+ "name": "subspecies",
+ "author": "subspecies author",
+ "source": "subspecies source",
+ "commonname": "subspecies common name"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/config/default_trees/mapping_files/col2021_polypodiopsida.json b/config/default_trees/mapping_files/col2021_polypodiopsida.json
new file mode 100644
index 00000000000..7853736c746
--- /dev/null
+++ b/config/default_trees/mapping_files/col2021_polypodiopsida.json
@@ -0,0 +1,94 @@
+{
+ "all_columns": [
+ "kingdom",
+ "phylum",
+ "class",
+ "order",
+ "family",
+ "genus",
+ "species",
+ "subspecies",
+ "family common name",
+ "species author",
+ "species source",
+ "species common name",
+ "subspecies author",
+ "subspecies source",
+ "subspecies common name"
+ ],
+ "ranks": [
+ {
+ "name": "Kingdom",
+ "rank": 10,
+ "column": "kingdom",
+ "fields": {
+ "name": "kingdom"
+ }
+ },
+ {
+ "name": "Phylum",
+ "rank": 30,
+ "column": "phylum",
+ "fields": {
+ "name": "phylum"
+ }
+ },
+ {
+ "name": "Class",
+ "rank": 60,
+ "column": "class",
+ "fields": {
+ "name": "class"
+ }
+ },
+ {
+ "name": "Order",
+ "rank": 100,
+ "column": "order",
+ "fields": {
+ "name": "order"
+ }
+ },
+ {
+ "name": "Family",
+ "rank": 140,
+ "column": "family",
+ "fields": {
+ "name": "family"
+ }
+ },
+ {
+ "name": "Genus",
+ "rank": 180,
+ "column": "genus",
+ "infullname": true,
+ "fields": {
+ "name": "genus"
+ }
+ },
+ {
+ "name": "Species",
+ "rank": 220,
+ "column": "species",
+ "infullname": true,
+ "fields": {
+ "name": "species",
+ "author": "species author",
+ "source": "species source",
+ "commonname": "species common name"
+ }
+ },
+ {
+ "name": "Subspecies",
+ "rank": 230,
+ "column": "subspecies",
+ "infullname": true,
+ "fields": {
+ "name": "subspecies",
+ "author": "subspecies author",
+ "source": "subspecies source",
+ "commonname": "subspecies common name"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/config/default_trees/mapping_files/col2021_tracheophyta.json b/config/default_trees/mapping_files/col2021_tracheophyta.json
new file mode 100644
index 00000000000..7853736c746
--- /dev/null
+++ b/config/default_trees/mapping_files/col2021_tracheophyta.json
@@ -0,0 +1,94 @@
+{
+ "all_columns": [
+ "kingdom",
+ "phylum",
+ "class",
+ "order",
+ "family",
+ "genus",
+ "species",
+ "subspecies",
+ "family common name",
+ "species author",
+ "species source",
+ "species common name",
+ "subspecies author",
+ "subspecies source",
+ "subspecies common name"
+ ],
+ "ranks": [
+ {
+ "name": "Kingdom",
+ "rank": 10,
+ "column": "kingdom",
+ "fields": {
+ "name": "kingdom"
+ }
+ },
+ {
+ "name": "Phylum",
+ "rank": 30,
+ "column": "phylum",
+ "fields": {
+ "name": "phylum"
+ }
+ },
+ {
+ "name": "Class",
+ "rank": 60,
+ "column": "class",
+ "fields": {
+ "name": "class"
+ }
+ },
+ {
+ "name": "Order",
+ "rank": 100,
+ "column": "order",
+ "fields": {
+ "name": "order"
+ }
+ },
+ {
+ "name": "Family",
+ "rank": 140,
+ "column": "family",
+ "fields": {
+ "name": "family"
+ }
+ },
+ {
+ "name": "Genus",
+ "rank": 180,
+ "column": "genus",
+ "infullname": true,
+ "fields": {
+ "name": "genus"
+ }
+ },
+ {
+ "name": "Species",
+ "rank": 220,
+ "column": "species",
+ "infullname": true,
+ "fields": {
+ "name": "species",
+ "author": "species author",
+ "source": "species source",
+ "commonname": "species common name"
+ }
+ },
+ {
+ "name": "Subspecies",
+ "rank": 230,
+ "column": "subspecies",
+ "infullname": true,
+ "fields": {
+ "name": "subspecies",
+ "author": "subspecies author",
+ "source": "subspecies source",
+ "commonname": "subspecies common name"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/config/default_trees/mapping_files/geography.json b/config/default_trees/mapping_files/geography.json
new file mode 100644
index 00000000000..6dc5528c6bb
--- /dev/null
+++ b/config/default_trees/mapping_files/geography.json
@@ -0,0 +1,60 @@
+{
+ "all_columns": [
+ "Continent",
+ "Country",
+ "State",
+ "County",
+ "GeographyCode",
+ "CentroidLat",
+ "CentroidLon"
+ ],
+ "root": {
+ "name": "Earth"
+ },
+ "ranks": [
+ {
+ "name": "Continent",
+ "rank": 100,
+ "column": "Continent",
+ "fields": {
+ "name": "Continent",
+ "geographycode": "GeographyCode",
+ "centroidlat": "CentroidLat",
+ "centroidlon": "CentroidLon"
+ }
+ },
+ {
+ "name": "Country",
+ "rank": 200,
+ "column": "Country",
+ "fields": {
+ "name": "Country",
+ "geographycode": "GeographyCode",
+ "centroidlat": "CentroidLat",
+ "centroidlon": "CentroidLon"
+ }
+ },
+ {
+ "name": "State",
+ "rank": 300,
+ "column": "State",
+ "fields": {
+ "name": "State",
+ "geographycode": "GeographyCode",
+ "centroidlat": "CentroidLat",
+ "centroidlon": "CentroidLon"
+ }
+ },
+ {
+ "name": "County",
+ "rank": 400,
+ "column": "County",
+ "fields": {
+ "name": "County",
+ "geographycode": "GeographyCode",
+ "centroidlat": "CentroidLat",
+ "centroidlon": "CentroidLon"
+ }
+ }
+ ]
+}
diff --git a/config/default_trees/mapping_files/geologictimeperiod.json b/config/default_trees/mapping_files/geologictimeperiod.json
new file mode 100644
index 00000000000..a2a382ac47f
--- /dev/null
+++ b/config/default_trees/mapping_files/geologictimeperiod.json
@@ -0,0 +1,66 @@
+{
+ "all_columns": [
+ "Erathem/Era",
+ "System/Period",
+ "Series/Epoch",
+ "Stage/Age",
+ "Start Period",
+ "Start Uncertainty",
+ "End Period",
+ "End Uncertainty"
+ ],
+ "root": {
+ "name": "Time",
+ "fullnameseparator": ", "
+ },
+ "ranks": [
+ {
+ "name": "Erathem/Era",
+ "rank": 100,
+ "column": "Erathem/Era",
+ "fields": {
+ "name": "Erathem/Era",
+ "startperiod": "Start Period",
+ "startuncertainty": "Start Uncertainty",
+ "endperiod": "End Period",
+ "enduncertainty": "End Uncertainty"
+ }
+ },
+ {
+ "name": "System/Period",
+ "rank": 200,
+ "column": "System/Period",
+ "fields": {
+ "name": "System/Period",
+ "startperiod": "Start Period",
+ "startuncertainty": "Start Uncertainty",
+ "endperiod": "End Period",
+ "enduncertainty": "End Uncertainty"
+ }
+ },
+ {
+ "name": "Series/Epoch",
+ "rank": 300,
+ "column": "Series/Epoch",
+ "fields": {
+ "name": "Series/Epoch",
+ "startperiod": "Start Period",
+ "startuncertainty": "Start Uncertainty",
+ "endperiod": "End Period",
+ "enduncertainty": "End Uncertainty"
+ }
+ },
+ {
+ "name": "Stage/Age",
+ "rank": 400,
+ "column": "Stage/Age",
+ "fields": {
+ "name": "Stage/Age",
+ "startperiod": "Start Period",
+ "startuncertainty": "Start Uncertainty",
+ "endperiod": "End Period",
+ "enduncertainty": "End Uncertainty"
+ }
+ }
+ ]
+}
diff --git a/config/default_trees/mapping_files/insect.json b/config/default_trees/mapping_files/insect.json
new file mode 100644
index 00000000000..6f7bf647f6e
--- /dev/null
+++ b/config/default_trees/mapping_files/insect.json
@@ -0,0 +1,91 @@
+{
+ "all_columns": ["kingdom","phylum","class","order","superfamily","family","genus","species","subspecies","species author","species source","species lsid","species common name","family common name","subspecies author","subspecies source","subspecies lsid","subspecies common name"],
+ "ranks": [
+ {
+ "name": "Kingdom",
+ "rank": 10,
+ "column": "kingdom",
+ "fields": {
+ "name": "kingdom"
+ }
+ },
+ {
+ "name": "Phylum",
+ "rank": 30,
+ "column": "phylum",
+ "fields": {
+ "name": "phylum"
+ }
+ },
+ {
+ "name": "Class",
+ "rank": 60,
+ "column": "class",
+ "fields": {
+ "name": "class"
+ }
+ },
+ {
+ "name": "Order",
+ "rank": 100,
+ "column": "order",
+ "fields": {
+ "name": "order"
+ }
+ },
+ {
+ "name": "Superfamily",
+ "rank": 130,
+ "enforced": false,
+ "column": "superfamily",
+ "fields": {
+ "name": "superfamily"
+ }
+ },
+ {
+ "name": "Family",
+ "rank": 140,
+ "column": "family",
+ "fields": {
+ "name": "family",
+ "commonname": "family common name"
+ }
+ },
+ {
+ "name": "Genus",
+ "rank": 180,
+ "infullname": true,
+ "column": "genus",
+ "fields": {
+ "name": "genus"
+ }
+ },
+ {
+ "name": "Species",
+ "rank": 220,
+ "infullname": true,
+ "column": "species",
+ "fields": {
+ "name": "species",
+ "author": "species author",
+ "source": "species source",
+ "lsid": "species lsid",
+ "commonname": "species common name"
+ }
+ },
+ {
+ "name": "Subspecies",
+ "rank": 230,
+ "infullname": true,
+ "enforced": false,
+ "column": "subspecies",
+ "fields": {
+ "name": "subspecies",
+ "author": "subspecies author",
+ "source": "subspecies source",
+ "lsid": "subspecies lsid",
+ "commonname": "subspecies common name"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/config/default_trees/remote_taxonfiles.json b/config/default_trees/remote_taxonfiles.json
new file mode 100644
index 00000000000..b7d2cefa910
--- /dev/null
+++ b/config/default_trees/remote_taxonfiles.json
@@ -0,0 +1,112 @@
+[
+ {
+ "discipline": "fish",
+ "title": "Ichthyology",
+ "coverage": "",
+ "file": "https://specify-software-public.s3.us-east-1.amazonaws.com/default_trees/col2021_actinopterygii.csv",
+ "mappingFile": "https://specify-software-public.s3.us-east-1.amazonaws.com/default_trees/mapping_files/col2021_actinopterygii.json",
+ "src": "Catalog of Life 2021",
+ "size": 5821850,
+ "rows": 37959,
+ "description": "Actinopterygii: 488 families; 4908 genera; 32513 species."
+ },
+ {
+ "discipline": "herpetology",
+ "title": "Herpetology",
+ "coverage": "",
+ "file": "https://specify-software-public.s3.us-east-1.amazonaws.com/default_trees/col2021_amphibia_reptilia.csv",
+ "mappingFile": "https://specify-software-public.s3.us-east-1.amazonaws.com/default_trees/mapping_files/col2021_amphibia_reptilia.json",
+ "src": "Catalog of Life 2021",
+ "size": 3986066,
+ "rows": 24731,
+ "description": "Amphibs: 75 families; 565 genera; 8054 species. Reptiles: 91 families; 1224 genera; 11570 species; 3140 subspecies."
+ },
+ {
+ "discipline": "bird",
+ "title": "Aves",
+ "coverage": "",
+ "file": "https://specify-software-public.s3.us-east-1.amazonaws.com/default_trees/col2021_aves.csv",
+ "mappingFile": "https://specify-software-public.s3.us-east-1.amazonaws.com/default_trees/mapping_files/col2021_aves.json",
+ "src": "Catalog of Life 2021",
+ "size": 6723988,
+ "rows": 34039,
+ "description": "Aves: 230 families; 2254 genera; 10521 species; 20988 subspecies."
+ },
+ {
+ "discipline": "insect",
+ "title": "Entomology",
+ "coverage": "",
+ "file": "https://specify-software-public.s3.us-east-1.amazonaws.com/default_trees/col2008_orthoptera.csv",
+ "mappingFile": "https://files.specifysoftware.org/treerows/col2008_orthoptera.json",
+ "src": "Catalog of Life 2008",
+ "size": 9502720,
+ "rows": 25604,
+ "description": "Order Orthoptera only: 40 families; 4320 genera; 23042 species."
+ },
+ {
+ "discipline": "botany",
+ "title": "Botany (Bryophyta)",
+ "coverage": "",
+ "file": "https://specify-software-public.s3.us-east-1.amazonaws.com/default_trees/col2021_bryophyta.csv",
+ "mappingFile": "https://specify-software-public.s3.us-east-1.amazonaws.com/default_trees/mapping_files/col2021_bryophyta.json",
+ "src": "Catalog of Life 2021",
+ "size": 2219282,
+ "rows": 14426,
+ "description": "Phylum Bryophyta only: 96 families; 941 genera; 13362 species."
+ },
+ {
+ "discipline": "botany",
+ "title": "Fungi",
+ "coverage": "",
+ "file": "https://specify-software-public.s3.us-east-1.amazonaws.com/default_trees/col2021_fungi.csv",
+ "mappingFile": "https://specify-software-public.s3.us-east-1.amazonaws.com/default_trees/mapping_files/col2021_fungi.json",
+ "src": "Catalog of Life 2021",
+ "size": 25417818,
+ "rows": 159229,
+ "description": "Fungi: 870 families; 11847 genera; 146154 species; 47 subspecies."
+ },
+ {
+ "discipline": "mammal",
+ "title": "Mammalogy",
+ "coverage": "",
+ "file": "https://specify-software-public.s3.us-east-1.amazonaws.com/default_trees/col2021_mammalia.csv",
+ "mappingFile": "https://specify-software-public.s3.us-east-1.amazonaws.com/default_trees/mapping_files/col2021_mammalia.json",
+ "src": "Catalog of Life 2021",
+ "size": 2417890,
+ "rows": 13557,
+ "description": "146 families; 1151 genera; 4832 species."
+ },
+ {
+ "discipline": "invertebrate",
+ "title": "Invertebrate Zoology",
+ "coverage": "",
+ "file": "https://specify-software-public.s3.us-east-1.amazonaws.com/default_trees/col2021_mollusca.csv",
+ "mappingFile": "https://specify-software-public.s3.us-east-1.amazonaws.com/default_trees/mapping_files/col2021_mollusca.json",
+ "src": "Catalog of Life 2021",
+ "size": 11614254,
+ "rows": 79277,
+ "description": "Phyla Mollusca: 1600 families; 14083 genera; 112144 species; 8580 subspecies."
+ },
+ {
+ "discipline": "botany",
+ "title": "Polypodiopsida",
+ "coverage": "",
+ "file": "https://specify-software-public.s3.us-east-1.amazonaws.com/default_trees/col2021_polypodiopsida.csv",
+ "mappingFile": "https://specify-software-public.s3.us-east-1.amazonaws.com/default_trees/mapping_files/col2021_polypodiopsida.json",
+ "src": "Catalog of Life 2021",
+ "size": 2177153,
+ "rows": 13072,
+ "description": "Polypodiopsida: fern allies."
+ },
+ {
+ "discipline": "botany",
+ "title": "Tracheophyta",
+ "coverage": "",
+ "file": "https://specify-software-public.s3.us-east-1.amazonaws.com/default_trees/col2021_tracheophyta.csv",
+ "mappingFile": "https://specify-software-public.s3.us-east-1.amazonaws.com/default_trees/mapping_files/col2021_tracheophyta.json",
+ "src": "Catalog of Life 2021",
+ "size": 62080445,
+ "rows": 395041,
+ "description": "Tracheophyta: vascular plants."
+ }
+]
diff --git a/config/default_trees/taxonfiles.json b/config/default_trees/taxonfiles.json
new file mode 100644
index 00000000000..9d4f374acdd
--- /dev/null
+++ b/config/default_trees/taxonfiles.json
@@ -0,0 +1,112 @@
+[
+ {
+ "discipline": "fish",
+ "title": "Ichthyology",
+ "coverage": "",
+ "file": "/static/config/default_trees/col2021_actinopterygii.csv.gz",
+ "mappingFile": "/static/config/default_trees/mapping_files/col2021_actinopterygii.json",
+ "src": "Catalog of Life 2021",
+ "size": 5821850,
+ "rows": 37959,
+ "description": "Actinopterygii: 488 families; 4908 genera; 32513 species."
+ },
+ {
+ "discipline": "herpetology",
+ "title": "Herpetology",
+ "coverage": "",
+ "file": "/static/config/default_trees/col2021_amphibia_reptilia.csv.gz",
+ "mappingFile": "/static/config/default_trees/mapping_files/col2021_amphibia_reptilia.json",
+ "src": "Catalog of Life 2021",
+ "size": 3986066,
+ "rows": 24731,
+ "description": "Amphibs: 75 families; 565 genera; 8054 species. Reptiles: 91 families; 1224 genera; 11570 species; 3140 subspecies."
+ },
+ {
+ "discipline": "bird",
+ "title": "Aves",
+ "coverage": "",
+ "file": "/static/config/default_trees/col2021_aves.csv.gz",
+ "mappingFile": "/static/config/default_trees/mapping_files/col2021_aves.json",
+ "src": "Catalog of Life 2021",
+ "size": 6723988,
+ "rows": 34039,
+ "description": "Aves: 230 families; 2254 genera; 10521 species; 20988 subspecies."
+ },
+ {
+ "discipline": "insect",
+ "title": "Entomology",
+ "coverage": "",
+ "file": "/static/config/default_trees/col2008_orthoptera.csv.gz",
+ "mappingFile": "/static/config/default_trees/mapping_files/insect.json",
+ "src": "Catalog of Life 2008",
+ "size": 9502720,
+ "rows": 25604,
+ "description": "Order Orthoptera only: 40 families; 4320 genera; 23042 species."
+ },
+ {
+ "discipline": "botany",
+ "title": "Botany (Bryophyta)",
+ "coverage": "",
+ "file": "/static/config/default_trees/col2021_bryophyta.csv.gz",
+ "mappingFile": "/static/config/default_trees/mapping_files/col2021_bryophyta.json",
+ "src": "Catalog of Life 2021",
+ "size": 2219282,
+ "rows": 14426,
+ "description": "Phylum Bryophyta only: 96 families; 941 genera; 13362 species."
+ },
+ {
+ "discipline": "botany",
+ "title": "Fungi",
+ "coverage": "",
+ "file": "/static/config/default_trees/col2021_fungi.csv.gz",
+ "mappingFile": "/static/config/default_trees/mapping_files/col2021_fungi.json",
+ "src": "Catalog of Life 2021",
+ "size": 25417818,
+ "rows": 159229,
+ "description": "Fungi: 870 families; 11847 genera; 146154 species; 47 subspecies."
+ },
+ {
+ "discipline": "mammal",
+ "title": "Mammalogy",
+ "coverage": "",
+ "file": "/static/config/default_trees/col2021_mammalia.csv.gz",
+ "mappingFile": "/static/config/default_trees/mapping_files/col2021_mammalia.json",
+ "src": "Catalog of Life 2021",
+ "size": 2417890,
+ "rows": 13557,
+ "description": "146 families; 1151 genera; 4832 species."
+ },
+ {
+ "discipline": "invertebrate",
+ "title": "Invertebrate Zoology",
+ "coverage": "",
+ "file": "/static/config/default_trees/col2021_mollusca.csv.gz",
+ "mappingFile": "/static/config/default_trees/mapping_files/col2021_mollusca.json",
+ "src": "Catalog of Life 2021",
+ "size": 11614254,
+ "rows": 79277,
+ "description": "Phyla Mollusca: 1600 families; 14083 genera; 112144 species; 8580 subspecies."
+ },
+ {
+ "discipline": "botany",
+ "title": "Polypodiopsida",
+ "coverage": "",
+ "file": "/static/config/default_trees/col2021_polypodiopsida.csv.gz",
+ "mappingFile": "/static/config/default_trees/mapping_files/col2021_polypodiopsida.json",
+ "src": "Catalog of Life 2021",
+ "size": 2177153,
+ "rows": 13072,
+ "description": "Polypodiopsida: fern allies."
+ },
+ {
+ "discipline": "botany",
+ "title": "Tracheophyta",
+ "coverage": "",
+ "file": "/static/config/default_trees/col2021_tracheophyta.csv.gz",
+ "mappingFile": "/static/config/default_trees/mapping_files/col2021_tracheophyta.json",
+ "src": "Catalog of Life 2021",
+ "size": 62080445,
+ "rows": 395041,
+ "description": "Tracheophyta: vascular plants."
+ }
+]
diff --git a/config/default_trees/test22_fishes.csv.gz b/config/default_trees/test22_fishes.csv.gz
new file mode 100644
index 00000000000..c97151aa71b
Binary files /dev/null and b/config/default_trees/test22_fishes.csv.gz differ
diff --git a/config/default_trees/test_fishes.csv.gz b/config/default_trees/test_fishes.csv.gz
new file mode 100644
index 00000000000..465daff55ab
Binary files /dev/null and b/config/default_trees/test_fishes.csv.gz differ
diff --git a/specifyweb/backend/setup_tool/tree_defaults.py b/specifyweb/backend/setup_tool/tree_defaults.py
index 18b4d70166e..ae1a5f203eb 100644
--- a/specifyweb/backend/setup_tool/tree_defaults.py
+++ b/specifyweb/backend/setup_tool/tree_defaults.py
@@ -2,9 +2,9 @@
from typing import Type, Optional
from pathlib import Path
from uuid import uuid4
-import requests
from .utils import load_json_from_file
+from specifyweb.backend.trees.default_tree_files import load_default_tree_json
from specifyweb.backend.trees.defaults import initialize_default_tree, create_default_tree_task, queue_create_default_tree_task
from specifyweb.backend.trees.utils import TREE_NAMES
@@ -19,14 +19,14 @@
'Lithostrat': Path(__file__).parent.parent.parent.parent / 'config' / 'common' / 'lithostrat_tree.json',
'Tectonicunit': Path(__file__).parent.parent.parent.parent / 'config' / 'common' / 'tectonicunit_tree.json'
}
-DEFAULT_TAXON_TREE_LIST_URL = 'https://files.specifysoftware.org/taxonfiles/taxonfiles.json'
+DEFAULT_TAXON_TREE_LIST_URL = '/static/config/default_trees/taxonfiles.json'
DEFAULT_TREE_URLS = {
- 'Geography': 'https://files.specifysoftware.org/geographyfiles/geonames.csv',
- 'Geologictimeperiod': 'https://files.specifysoftware.org/chronostratfiles/GeologicTimePeriod.csv',
+ 'Geography': 'default_trees/geonames.csv.gz',
+ 'Geologictimeperiod': 'default_trees/GeologicTimePeriod.csv.gz',
}
DEFAULT_TREE_MAPPING_URLS = {
- 'Geography': 'https://files.specifysoftware.org/treerows/geography.json',
- 'Geologictimeperiod': 'https://files.specifysoftware.org/treerows/geologictimeperiod.json',
+ 'Geography': 'default_trees/mapping_files/geography.json',
+ 'Geologictimeperiod': 'default_trees/mapping_files/geologictimeperiod.json',
}
def start_default_tree_from_configuration(tree_type: str, kwargs: dict, user_rank_cfg: dict):
@@ -113,9 +113,7 @@ def start_preload_default_tree(tree_type: str, discipline_id: Optional[int], col
logger.warning(f'Can\'t preload tree, no default tree URLs for {tree_discipline_name} tree.')
return
- resp = requests.get(mapping_url)
- resp.raise_for_status()
- tree_cfg = resp.json()
+ tree_cfg = load_default_tree_json(mapping_url)
task_id = str(uuid4())
async_result = create_default_tree_task.apply_async(
@@ -134,4 +132,4 @@ def start_preload_default_tree(tree_type: str, discipline_id: Optional[int], col
def update_tree_scoping(treedef: Type[DjangoModel], discipline_id: int):
"""Trees may be created before a discipline is created. This will update their discipline."""
setattr(treedef, "discipline_id", discipline_id)
- treedef.save(update_fields=["discipline_id"])
\ No newline at end of file
+ treedef.save(update_fields=["discipline_id"])
diff --git a/specifyweb/backend/trees/default_tree_files.py b/specifyweb/backend/trees/default_tree_files.py
new file mode 100644
index 00000000000..fbcfe775eec
--- /dev/null
+++ b/specifyweb/backend/trees/default_tree_files.py
@@ -0,0 +1,177 @@
+import csv
+import gzip
+import json
+import time
+from pathlib import Path
+from typing import Dict, Iterator, Optional
+from urllib.parse import unquote, urlparse
+
+import requests
+from django.conf import settings
+from requests.exceptions import ChunkedEncodingError, ConnectionError
+
+
+STATIC_CONFIG_PREFIX = '/static/config/'
+DEFAULT_TREE_MAPPING_DIR = 'default_trees/mapping_files'
+
+
+def _config_root() -> Path:
+ return Path(settings.SPECIFY_CONFIG_DIR).resolve()
+
+
+def _resolve_config_path(relative_path: str) -> Optional[Path]:
+ config_root = _config_root()
+ candidate = (config_root / relative_path).resolve()
+ try:
+ candidate.relative_to(config_root)
+ except ValueError:
+ return None
+ if not candidate.is_file() and candidate.suffix == '.csv':
+ gz_candidate = candidate.with_name(f'{candidate.name}.gz')
+ if gz_candidate.is_file():
+ return gz_candidate
+ return candidate
+
+
+def _legacy_default_tree_relative_path(source: str) -> Optional[str]:
+ parsed = urlparse(source)
+ path = unquote(parsed.path)
+
+ if path.startswith(STATIC_CONFIG_PREFIX):
+ return path[len(STATIC_CONFIG_PREFIX):]
+
+ if parsed.netloc == 'specify-software-public.s3.us-east-1.amazonaws.com':
+ if path.startswith('/default_trees/'):
+ return path.lstrip('/')
+ return None
+
+ if parsed.netloc != 'files.specifysoftware.org':
+ return None
+
+ filename = Path(path).name
+ if path.startswith('/taxonfiles/'):
+ return f'default_trees/{filename}'
+ if path.startswith('/geographyfiles/'):
+ return f'default_trees/{filename}'
+ if path.startswith('/chronostratfiles/'):
+ return f'default_trees/{filename}'
+ if path.startswith('/treerows/'):
+ if filename == 'col2008_orthoptera.json':
+ filename = 'insect.json'
+ return f'{DEFAULT_TREE_MAPPING_DIR}/{filename}'
+ return None
+
+
+def get_local_default_tree_path(source: str) -> Optional[Path]:
+ if not source:
+ return None
+
+ parsed = urlparse(source)
+ parsed_path = unquote(parsed.path or source)
+
+ if parsed.scheme in ('http', 'https'):
+ relative_path = _legacy_default_tree_relative_path(source)
+ if relative_path is None:
+ return None
+ return _resolve_config_path(relative_path)
+
+ if parsed_path.startswith(STATIC_CONFIG_PREFIX):
+ return _resolve_config_path(parsed_path[len(STATIC_CONFIG_PREFIX):])
+
+ if parsed_path.startswith('config/'):
+ return _resolve_config_path(parsed_path[len('config/'):])
+
+ path = Path(parsed_path)
+ if path.is_absolute():
+ resolved = path.resolve()
+ try:
+ resolved.relative_to(_config_root())
+ except ValueError:
+ return None
+ return resolved
+
+ return _resolve_config_path(parsed_path)
+
+
+def load_default_tree_json(source: str):
+ local_path = get_local_default_tree_path(source)
+ if local_path is not None:
+ if not local_path.is_file():
+ raise FileNotFoundError(f'Default tree JSON file not found: {local_path}')
+ with local_path.open('r', encoding='utf-8') as file_handle:
+ return json.load(file_handle)
+
+ parsed = urlparse(source)
+ if parsed.scheme not in ('http', 'https'):
+ raise FileNotFoundError(f'Default tree JSON file not found: {source}')
+
+ resp = requests.get(source, timeout=(5, 30))
+ resp.raise_for_status()
+ return resp.json()
+
+
+def stream_default_tree_csv(source: str) -> Iterator[Dict[str, str]]:
+ local_path = get_local_default_tree_path(source)
+ if local_path is not None:
+ if not local_path.is_file():
+ raise FileNotFoundError(f'Default tree CSV file not found: {local_path}')
+ open_file = gzip.open if local_path.suffix == '.gz' else Path.open
+ with open_file(local_path, 'rt', encoding='utf-8-sig', newline='') as file_handle:
+ yield from csv.DictReader(file_handle)
+ return
+
+ parsed = urlparse(source)
+ if parsed.scheme not in ('http', 'https'):
+ raise FileNotFoundError(f'Default tree CSV file not found: {source}')
+
+ yield from _stream_remote_csv(source)
+
+
+def _stream_remote_csv(source: str) -> Iterator[Dict[str, str]]:
+ chunk_size = 8192
+ max_retries = 10
+
+ def lines_iter() -> Iterator[str]:
+ buffer = b""
+ bytes_downloaded = 0
+ retries = 0
+
+ headers = {}
+ while True:
+ if bytes_downloaded > 0:
+ headers['Range'] = f'bytes={bytes_downloaded}-'
+
+ try:
+ with requests.get(
+ source,
+ stream=True,
+ timeout=(5, 30),
+ headers=headers,
+ ) as resp:
+ resp.raise_for_status()
+ for chunk in resp.iter_content(chunk_size=chunk_size):
+ chunk_length = len(chunk)
+ if chunk_length == 0:
+ continue
+ buffer += chunk
+ bytes_downloaded += chunk_length
+
+ while True:
+ new_line_index = buffer.find(b'\n')
+ if new_line_index == -1:
+ break
+ line = buffer[: new_line_index + 1]
+ buffer = buffer[new_line_index + 1 :]
+ yield line.decode('utf-8-sig', errors='replace')
+
+ if buffer:
+ yield buffer.decode('utf-8-sig', errors='replace')
+ return
+ except (ChunkedEncodingError, ConnectionError):
+ if retries < max_retries:
+ retries += 1
+ time.sleep(2**retries)
+ continue
+ raise
+
+ yield from csv.DictReader(lines_iter())
diff --git a/specifyweb/backend/trees/defaults.py b/specifyweb/backend/trees/defaults.py
index b6482ed1ad1..a16c659a0d4 100644
--- a/specifyweb/backend/trees/defaults.py
+++ b/specifyweb/backend/trees/defaults.py
@@ -1,16 +1,12 @@
-from typing import Any, Callable, List, Dict, Iterator, Optional, TypedDict, NotRequired
+from typing import Any, Dict, Optional, TypedDict, NotRequired
import json
-import requests
-import csv
-import time
-from requests.exceptions import ChunkedEncodingError, ConnectionError
from django.db import transaction
-from django.conf import settings
from specifyweb.backend.notifications.models import Message
from specifyweb.celery_tasks import LogErrorsTask, app
import specifyweb.specify.models as spmodels
+from specifyweb.backend.trees.default_tree_files import stream_default_tree_csv
from specifyweb.backend.trees.utils import get_models, TREE_ROOT_NODES
from specifyweb.backend.trees.extras import renumber_tree, set_fullnames
from specifyweb.backend.redis_cache.store import add_to_set, remove_from_set, set_members
@@ -432,7 +428,7 @@ def progress(cur: int, additional_total: int=0) -> None:
total_rows = row_count-2
progress(0, total_rows)
- for row in stream_csv_from_url(url):
+ for row in stream_default_tree_csv(url):
add_default_tree_record(context, row, tree_cfg, current)
context.flush()
progress(1, 0)
@@ -468,59 +464,3 @@ def progress(cur: int, additional_total: int=0) -> None:
)
finish_create_default_tree_task(f'create_default_tree_{tree_type}_{existing_tree_def_id or self.request.id}')
-
-def stream_csv_from_url(url: str) -> Iterator[Dict[str, str]]:
- """
- Streams a taxon CSV from a URL. Yields each row.
- """
- chunk_size = 8192
- max_retries = 10
-
- def lines_iter() -> Iterator[str]:
- # Streams data from the server in -chunks-, yields -lines-.
- buffer = b""
- bytes_downloaded = 0
- retries = 0
-
- headers = {}
- while True:
- # Request data starting from the last downloaded bytes
- if bytes_downloaded > 0:
- headers['Range'] = f'bytes={bytes_downloaded}-'
-
- try:
- with requests.get(url, stream=True, timeout=(5, 30), headers=headers) as resp:
- resp.raise_for_status()
- for chunk in resp.iter_content(chunk_size=chunk_size):
- chunk_length = len(chunk)
- if chunk_length == 0:
- continue
- buffer += chunk
- bytes_downloaded += chunk_length
-
- # Extract all lines from chunk
- while True:
- new_line_index = buffer.find(b'\n')
- if new_line_index == -1: break
- line = buffer[:new_line_index + 1] # extract line
- buffer = buffer[new_line_index + 1 :] # clear read buffer
- yield line.decode('utf-8-sig', errors='replace')
-
- if buffer:
- # yield last line
- yield buffer.decode('utf-8-sig', errors='replace')
- return
- except (ChunkedEncodingError, ConnectionError) as e:
- # Trigger retry
- if retries < max_retries:
- retries += 1
- time.sleep(2 ** retries)
- continue
- raise
- except Exception:
- raise
-
- reader = csv.DictReader(lines_iter())
-
- for row in reader:
- yield row
\ No newline at end of file
diff --git a/specifyweb/backend/trees/views.py b/specifyweb/backend/trees/views.py
index f636c869944..75e9bcf2141 100644
--- a/specifyweb/backend/trees/views.py
+++ b/specifyweb/backend/trees/views.py
@@ -1,13 +1,11 @@
-import csv
from functools import wraps
import json
from uuid import uuid4
from django import http
-from typing import Iterator, Literal, TypedDict, Any, Dict
+from typing import Literal, TypedDict, Any
from django.db import connection, transaction
from django.http import HttpResponse
from django.views.decorators.http import require_POST
-import requests
from specifyweb.backend.trees.tree_mutations import perm_target
from specifyweb.specify.views import login_maybe_required, openapi
from sqlalchemy import distinct
@@ -29,6 +27,7 @@
from specifyweb.backend.notifications.models import Message
from specifyweb.backend.trees.utils import get_search_filters
+from specifyweb.backend.trees.default_tree_files import load_default_tree_json
from specifyweb.backend.trees.defaults import create_default_tree_task, queue_create_default_tree_task, get_active_create_default_tree_tasks
from specifyweb.specify.utils.field_change_info import FieldChangeInfo
from specifyweb.backend.trees.ranks import tree_rank_count
@@ -758,9 +757,7 @@ def create_default_tree_view(request):
mapping_url = data.get('mappingUrl', None)
if mapping_url:
try:
- resp = requests.get(mapping_url)
- resp.raise_for_status()
- tree_cfg = resp.json()
+ tree_cfg = load_default_tree_json(mapping_url)
except Exception:
return http.JsonResponse({'error': f'Could not retrieve default tree mapping from {mapping_url}.'}, status=404)
try:
@@ -917,12 +914,11 @@ def default_tree_mapping(request) -> http.HttpResponse:
"""Retrieves a default populated tree's mapping from a url"""
# TODO: Reuse code from create_default_tree
data = json.loads(request.body)
+ tree_cfg = data.get("mapping")
mapping_url = data.get("mappingUrl")
if mapping_url:
try:
- resp = requests.get(mapping_url)
- resp.raise_for_status()
- tree_cfg = resp.json()
+ tree_cfg = load_default_tree_json(mapping_url)
except Exception:
return http.JsonResponse({'error': f'Could not retrieve default tree mapping from {mapping_url}.'}, status=404)
try:
diff --git a/specifyweb/frontend/js_src/lib/components/Leaflet/layers.ts b/specifyweb/frontend/js_src/lib/components/Leaflet/layers.ts
index 7b6fe4c3f70..2babf928e06 100644
--- a/specifyweb/frontend/js_src/lib/components/Leaflet/layers.ts
+++ b/specifyweb/frontend/js_src/lib/components/Leaflet/layers.ts
@@ -22,7 +22,7 @@ type SerializedLayer = {
type Layers = RR<'baseMaps' | 'overlays', IR>;
export const leafletLayersEndpoint =
- 'https://files.specifysoftware.org/specify7/7.8.10/leaflet-layers.json';
+ '/static/config/common/leaflet-layers.json';
/**
* Optional filters to apply to a layer
@@ -47,7 +47,7 @@ export const preferredOverlay = 'Labels and boundaries';
* leafletLayersEndpoint (defined above)
* * User didn't define a resource file `leaflet-layers`
*
- * On any updates to this file, you should also update the one at
+ * On any updates to the checked-in fallback config, you should also update
* leafletLayersEndpoint
*
* Documentation:
diff --git a/specifyweb/frontend/js_src/lib/components/TreeView/CreateTree.tsx b/specifyweb/frontend/js_src/lib/components/TreeView/CreateTree.tsx
index ca214d44a11..57cfc8fce7e 100644
--- a/specifyweb/frontend/js_src/lib/components/TreeView/CreateTree.tsx
+++ b/specifyweb/frontend/js_src/lib/components/TreeView/CreateTree.tsx
@@ -60,9 +60,7 @@ export type TreeCreationProgressInfo = {
};
export async function fetchDefaultTrees(): Promise {
- const response = await fetch(
- 'https://files.specifysoftware.org/taxonfiles/taxonfiles.json'
- );
+ const response = await fetch('/static/config/default_trees/taxonfiles.json');
if (!response.ok) {
throw new Error(
`Failed to fetch default trees: ${response.status} ${response.statusText}`