From 1b9c7884c1528e83d4e6ad4319dff43c92640485 Mon Sep 17 00:00:00 2001 From: Lukasz Debek Date: Thu, 18 Jul 2024 10:17:51 +0200 Subject: [PATCH] Added additional fields. (#5) * Added additional fields. * Added additional fields. * metadata.txt update. --- NnpaReporting/fields_dialog.py | 70 ++++++++++------ NnpaReporting/fields_dialog.ui | 63 +++++++++++++-- NnpaReporting/metadata.txt | 3 +- NnpaReporting/output_dialog.py | 121 ++++++++++++++++++---------- NnpaReporting/reporting_map_tool.py | 21 +++-- NnpaReporting/reporting_tool.py | 43 ++++++---- NnpaReporting/settings_dialog.py | 22 +++-- 7 files changed, 229 insertions(+), 114 deletions(-) diff --git a/NnpaReporting/fields_dialog.py b/NnpaReporting/fields_dialog.py index b8c6d31..c79af5e 100644 --- a/NnpaReporting/fields_dialog.py +++ b/NnpaReporting/fields_dialog.py @@ -1,9 +1,9 @@ from os import path +from qgis.core import QgsFields, QgsSettings from qgis.PyQt import uic from qgis.PyQt.QtCore import QVariant from qgis.PyQt.QtWidgets import QDialog -from qgis.core import QgsSettings, QgsFields ui_file = path.join(path.dirname(__file__), "fields_dialog.ui") @@ -16,34 +16,58 @@ def __init__(self, parent, fields: QgsFields): for field in fields: if field.type() != QVariant.String: continue - self.ui.nameComboBox.addItem(field.name()) - self.ui.dateComboBox.addItem(field.name()) - self.ui.precisionComboBox.addItem(field.name()) - - self.ui.nameComboBox.setCurrentIndex(-1) - self.ui.dateComboBox.setCurrentIndex(-1) - self.ui.precisionComboBox.setCurrentIndex(-1) + for field_combo_box in self.field_selection_widgets: + field_combo_box.addItem(field.name()) + for field_combo_box in self.field_selection_widgets: + field_combo_box.setCurrentIndex(-1) s = QgsSettings() - nameFieldName = s.value('plugins/nnpa_reporting_plugin/name_field_name', "Common_Nam") - dateFieldName = s.value('plugins/nnpa_reporting_plugin/date_field_name', "Sample_Dat") - precisionFieldName = s.value('plugins/nnpa_reporting_plugin/precision_field_name', "Precision") + name_field_name = s.value("plugins/nnpa_reporting_plugin/name_field_name", "Common_Nam") + date_field_name = s.value("plugins/nnpa_reporting_plugin/date_field_name", "Sample_Dat") + precision_field_name = s.value("plugins/nnpa_reporting_plugin/precision_field_name", "Precision") + grid_refer_field_name = s.value("plugins/nnpa_reporting_plugin/grid_refer_field_name", "grid refer") + latin_name_field_name = s.value("plugins/nnpa_reporting_plugin/latin_name_field_name", "latin name") + recorder_field_name = s.value("plugins/nnpa_reporting_plugin/recorder_field_name", "recorder") + survey_field_name = s.value("plugins/nnpa_reporting_plugin/survey_field_name", "survey nam") + + self.ui.name_cbo.setCurrentText(name_field_name) + self.ui.date_cbo.setCurrentText(date_field_name) + self.ui.precision_cbo.setCurrentText(precision_field_name) + self.ui.grid_refer_cbo.setCurrentText(grid_refer_field_name) + self.ui.latin_name_cbo.setCurrentText(latin_name_field_name) + self.ui.recorder_cbo.setCurrentText(recorder_field_name) + self.ui.survey_cbo.setCurrentText(survey_field_name) - self.ui.nameComboBox.setCurrentText(nameFieldName) - self.ui.dateComboBox.setCurrentText(dateFieldName) - self.ui.precisionComboBox.setCurrentText(precisionFieldName) + @property + def field_selection_widgets(self): + field_combo_boxes = [ + self.ui.name_cbo, + self.ui.date_cbo, + self.ui.precision_cbo, + self.ui.grid_refer_cbo, + self.ui.latin_name_cbo, + self.ui.recorder_cbo, + self.ui.survey_cbo, + ] + return field_combo_boxes def accept(self): - if self.ui.nameComboBox.currentIndex() < 0 or \ - self.ui.dateComboBox.currentIndex() < 0 or \ - self.ui.precisionComboBox.currentIndex() < 0: + if any(combo_box.currentIndex() < 0 for combo_box in self.field_selection_widgets): return - nameFieldName = self.ui.nameComboBox.currentText() - dateFieldName = self.ui.dateComboBox.currentText() - precisionFieldName = self.ui.precisionComboBox.currentText() + name_field_name = self.ui.name_cbo.currentText() + date_field_name = self.ui.date_cbo.currentText() + precision_field_name = self.ui.precision_cbo.currentText() + grid_refer_field_name = self.ui.grid_refer_cbo.currentText() + latin_name_field_name = self.ui.latin_name_cbo.currentText() + recorder_field_name = self.ui.recorder_cbo.currentText() + survey_field_name = self.ui.survey_cbo.currentText() s = QgsSettings() - s.setValue('plugins/nnpa_reporting_plugin/name_field_name', nameFieldName) - s.setValue('plugins/nnpa_reporting_plugin/date_field_name', dateFieldName) - s.setValue('plugins/nnpa_reporting_plugin/precision_field_name', precisionFieldName) + s.setValue("plugins/nnpa_reporting_plugin/name_field_name", name_field_name) + s.setValue("plugins/nnpa_reporting_plugin/date_field_name", date_field_name) + s.setValue("plugins/nnpa_reporting_plugin/precision_field_name", precision_field_name) + s.setValue("plugins/nnpa_reporting_plugin/grid_refer_field_name", grid_refer_field_name) + s.setValue("plugins/nnpa_reporting_plugin/latin_name_field_name", latin_name_field_name) + s.setValue("plugins/nnpa_reporting_plugin/recorder_field_name", recorder_field_name) + s.setValue("plugins/nnpa_reporting_plugin/survey_field_name", survey_field_name) super().accept() diff --git a/NnpaReporting/fields_dialog.ui b/NnpaReporting/fields_dialog.ui index a2e7c2d..07efe08 100644 --- a/NnpaReporting/fields_dialog.ui +++ b/NnpaReporting/fields_dialog.ui @@ -6,8 +6,8 @@ 0 0 - 460 - 180 + 475 + 250 @@ -22,7 +22,7 @@ - + @@ -32,7 +32,7 @@ - + @@ -42,9 +42,9 @@ - + - + Qt::Horizontal @@ -54,7 +54,19 @@ - + + + + + + + + + + + + + Qt::Vertical @@ -67,8 +79,45 @@ + + + + Grid Reference + + + + + + + Latin Name + + + + + + + Sample Recorder + + + + + + + Survey Name + + + + + name_cbo + date_cbo + precision_cbo + grid_refer_cbo + latin_name_cbo + recorder_cbo + survey_cbo + diff --git a/NnpaReporting/metadata.txt b/NnpaReporting/metadata.txt index 43e9f56..592d075 100644 --- a/NnpaReporting/metadata.txt +++ b/NnpaReporting/metadata.txt @@ -2,7 +2,7 @@ name=NNPA Reporting description=NNPA Reporting tool about=This plugin is used by NNPA to generate species reports based on specified filters -version=1.3 +version=1.4 qgisMinimumVersion=3.34 qgisMaximumVersion=3.99 author=Lutra Consulting @@ -11,6 +11,7 @@ tracker=https://github.com/lutraconsulting/qgis-nnpa-reporting/issues repository=https://github.com/lutraconsulting/qgis-nnpa-reporting changelog=

+

1.4 Added Grid Reference, Latin Name, Sample Recorder and Survey Name fields.

1.3 Identify by Point/Rectangle/Polygon/Polygon from a layer

1.2 Allow selecting layer fields

1.1 Support QGIS < 3.34 diff --git a/NnpaReporting/output_dialog.py b/NnpaReporting/output_dialog.py index aefc36b..776f3f3 100644 --- a/NnpaReporting/output_dialog.py +++ b/NnpaReporting/output_dialog.py @@ -1,10 +1,10 @@ -from os import path from enum import Enum +from os import path +from qgis.core import Qgis, QgsFeature, QgsFeatureRequest, QgsField, QgsProject, QgsSettings, QgsVectorLayer from qgis.PyQt import uic from qgis.PyQt.QtCore import QVariant from qgis.PyQt.QtWidgets import QDialog, QHeaderView, QTreeWidgetItem -from qgis.core import QgsFeatureRequest, QgsSettings, QgsVectorLayer, QgsField, QgsFeature, QgsProject, Qgis ui_file = path.join(path.dirname(__file__), "output_dialog.ui") @@ -34,18 +34,40 @@ def __init__(self, layer): "Observation Date", "Precision Min", "Precision Max", + "Grid Reference", + "Latin Name", + "Sample Recorder", + "Survey Name", ] ) self.treeResults.header().resizeSections(QHeaderView.ResizeToContents) s = QgsSettings() - self.excluded_names = s.value('plugins/nnpa_reporting_plugin/sensitive_species', []) - self.name_field_name = s.value('plugins/nnpa_reporting_plugin/name_field_name', "Common_Nam") - self.date_field_name = s.value('plugins/nnpa_reporting_plugin/date_field_name', "Sample_Dat") - self.precision_field_name = s.value('plugins/nnpa_reporting_plugin/precision_field_name', "Precision") + self.excluded_names = s.value("plugins/nnpa_reporting_plugin/sensitive_species", []) + self.name_field_name = s.value("plugins/nnpa_reporting_plugin/name_field_name", "Common_Nam") + self.date_field_name = s.value("plugins/nnpa_reporting_plugin/date_field_name", "Sample_Dat") + self.precision_field_name = s.value("plugins/nnpa_reporting_plugin/precision_field_name", "Precision") + self.grid_refer_field_name = s.value("plugins/nnpa_reporting_plugin/grid_refer_field_name", "grid refer") + self.latin_name_field_name = s.value("plugins/nnpa_reporting_plugin/latin_name_field_name", "latin name") + self.recorder_field_name = s.value("plugins/nnpa_reporting_plugin/recorder_field_name", "recorder") + self.survey_field_name = s.value("plugins/nnpa_reporting_plugin/survey_field_name", "survey nam") + self.precision_values = [] self.precision_unknown_values = set() self.populate_ranges() + @property + def desired_fields(self): + fields = [ + self.name_field_name, + self.date_field_name, + self.precision_field_name, + self.grid_refer_field_name, + self.latin_name_field_name, + self.recorder_field_name, + self.survey_field_name, + ] + return fields + def populate_ranges(self): """Populate the precision fields in the plugin dialog with the unique values""" self.populate_precision_values() @@ -76,33 +98,31 @@ def build_request(self): if self.cbIncludeNullPrecision.isChecked(): unwanted_string_prec = f"""'{"','".join(self.precision_unknown_values)}'""" - precision_filter = f"""({precision_filter} or """ \ - f""""{self.precision_field_name}" is null or """ \ - f""""{self.precision_field_name}" in ({unwanted_string_prec}))""" + precision_filter = ( + f"""({precision_filter} or """ + f""""{self.precision_field_name}" is null or """ + f""""{self.precision_field_name}" in ({unwanted_string_prec}))""" + ) # buffer self.buffer_value = self.qgsDoubleSpinBoxBuffer.value() # exclusion filter - excluded_names = ( - self.excluded_names if self.cbExcludeSensitive.isChecked() else [] - ) + excluded_names = self.excluded_names if self.cbExcludeSensitive.isChecked() else [] expression_filter = precision_filter if excluded_names: excluded_names_str = f"""'{"','".join(excluded_names)}'""" excluded_names_filter = f'"{self.name_field_name}" not in ({excluded_names_str})' expression_filter += f" and {excluded_names_filter}" - # desired fields - fields = [self.name_field_name, self.date_field_name, self.precision_field_name] - - return (QgsFeatureRequest() - .setDistanceWithin(self.geom, self.buffer_value) - .setFilterExpression(expression_filter) - .setFlags(QgsFeatureRequest.NoGeometry) - .setFlags(QgsFeatureRequest.ExactIntersect) - .setSubsetOfAttributes(fields, self.layer.fields()) - ) + return ( + QgsFeatureRequest() + .setDistanceWithin(self.geom, self.buffer_value) + .setFilterExpression(expression_filter) + .setFlags(QgsFeatureRequest.NoGeometry) + .setFlags(QgsFeatureRequest.ExactIntersect) + .setSubsetOfAttributes(self.desired_fields, self.layer.fields()) + ) def perform_request(self, req): """Format the features that are passing through the filter to the desired format""" @@ -112,6 +132,10 @@ def perform_request(self, req): # the first occurrence output_dict[sel_feat[self.name_field_name]] = { "date": [sel_feat[self.date_field_name]], + "grid_refer": [sel_feat[self.grid_refer_field_name]], + "latin_name": sel_feat[self.latin_name_field_name], + "recorder": [sel_feat[self.recorder_field_name]], + "survey_name": [sel_feat[self.survey_field_name]], "precision_min": sel_feat[self.precision_field_name], "precision_max": sel_feat[self.precision_field_name], "count": 1, @@ -122,21 +146,22 @@ def perform_request(self, req): dict_feature["count"] += 1 dict_feature["date"].append(sel_feat[self.date_field_name]) + dict_feature["grid_refer"].append(sel_feat[self.grid_refer_field_name]) + dict_feature["recorder"].append(sel_feat[self.recorder_field_name]) + dict_feature["survey_name"].append(sel_feat[self.survey_field_name]) try: - if ( - not dict_feature["precision_max"] - or self.precision_values.index(sel_feat[self.precision_field_name]) > self.precision_values.index(dict_feature["precision_max"]) - ): + if not dict_feature["precision_max"] or self.precision_values.index( + sel_feat[self.precision_field_name] + ) > self.precision_values.index(dict_feature["precision_max"]): dict_feature["precision_max"] = sel_feat[self.precision_field_name] except ValueError: pass try: - if ( - not dict_feature["precision_min"] - or self.precision_values.index(sel_feat[self.precision_field_name]) < self.precision_values.index(dict_feature["precision_min"]) - ): + if not dict_feature["precision_min"] or self.precision_values.index( + sel_feat[self.precision_field_name] + ) < self.precision_values.index(dict_feature["precision_min"]): dict_feature["precision_min"] = sel_feat[self.precision_field_name] except ValueError: pass @@ -154,8 +179,12 @@ def populate_results(self, feature_dict): key, str(value["count"]), ", ".join(set(value["date"])), - value["precision_min"] or 'NULL', - value["precision_max"] or 'NULL', + value["precision_min"] or "NULL", + value["precision_max"] or "NULL", + ", ".join(set(value["grid_refer"])), + value["latin_name"], + ", ".join(set(value["recorder"])), + ", ".join(set(value["survey_name"])), ] ) root_item.addChild(item) @@ -175,7 +204,7 @@ def populate_precision_values(self): precision_dict = {} for value in unique_list: try: - meters = value.upper().replace('KM', '000').replace('M', '').replace(' ', '') + meters = value.upper().replace("KM", "000").replace("M", "").replace(" ", "") meters = int(meters) except (ValueError, TypeError): # handle not identified values self.precision_unknown_values.add(value) @@ -208,23 +237,26 @@ def load_results_as_layer(self): vl.setCrs(self.layer.crs()) dp = vl.dataProvider() - dp.addAttributes([QgsField("Common Name", QVariant.String), - QgsField("Count", QVariant.Int), - QgsField("Observation Date", QVariant.String), - QgsField("Precision Min", QVariant.String), - QgsField("Precision Max", QVariant.String), - ]) + dp.addAttributes( + [ + QgsField("Common Name", QVariant.String), + QgsField("Count", QVariant.Int), + QgsField("Observation Date", QVariant.String), + QgsField("Precision Min", QVariant.String), + QgsField("Precision Max", QVariant.String), + QgsField("Grid Reference", QVariant.String), + QgsField("Latin Name", QVariant.String), + QgsField("Sample Recorder", QVariant.String), + QgsField("Survey Name", QVariant.String), + ] + ) vl.updateFields() root_item = self.treeResults.invisibleRootItem() for i in range(root_item.childCount()): item = root_item.child(i) f = QgsFeature(dp.fields()) - f.setAttributes([item.text(0), - item.text(1), - item.text(2), - item.text(3), - item.text(4)]) + f.setAttributes([item.text(i) for i in range(9)]) f.setGeometry(geom) dp.addFeature(f) @@ -237,6 +269,7 @@ def clearResults(self): class TreeWidgetItem(QTreeWidgetItem): """Custom class to enable sorting 'Count' column - integers converted to string""" + def __init__(self, parent=None): QTreeWidgetItem.__init__(self, parent) diff --git a/NnpaReporting/reporting_map_tool.py b/NnpaReporting/reporting_map_tool.py index ec36d2f..205e6ef 100644 --- a/NnpaReporting/reporting_map_tool.py +++ b/NnpaReporting/reporting_map_tool.py @@ -1,16 +1,9 @@ -from qgis.PyQt.QtCore import Qt, QPoint +from qgis.core import Qgis, QgsApplication, QgsCsException, QgsGeometry, QgsRectangle +from qgis.gui import QgsIdentifyMenu, QgsMapTool, QgsRubberBand +from qgis.PyQt.QtCore import QPoint, Qt from qgis.PyQt.QtGui import QColor -from qgis.gui import QgsIdentifyMenu -from qgis.core import ( - Qgis, - QgsRectangle, - QgsGeometry, - QgsApplication, - QgsCsException -) -from qgis.gui import QgsMapTool, QgsRubberBand -from .output_dialog import OutputDialog, IdentifyMode +from .output_dialog import IdentifyMode, OutputDialog class ReportingMapTool(QgsMapTool): @@ -75,7 +68,11 @@ def canvasReleaseEvent(self, e): identifyMenu.setExecWithSingleResult(True) results = QgsIdentifyMenu.findFeaturesOnCanvas(e, self.canvas(), [Qgis.GeometryType.Polygon]) # remove results from our own layer - results = [r for r in results if r.mLayer.dataProvider().dataSourceUri() != self.layer.dataProvider().dataSourceUri()] + results = [ + r + for r in results + if r.mLayer.dataProvider().dataSourceUri() != self.layer.dataProvider().dataSourceUri() + ] globalPos = self.canvas().mapToGlobal(QPoint(e.pos().x() + 5, e.pos().y() + 5)) selectedFeatures = identifyMenu.exec(results, globalPos) if selectedFeatures and selectedFeatures[0].mFeature.hasGeometry(): diff --git a/NnpaReporting/reporting_tool.py b/NnpaReporting/reporting_tool.py index f0abd25..9572692 100644 --- a/NnpaReporting/reporting_tool.py +++ b/NnpaReporting/reporting_tool.py @@ -1,8 +1,8 @@ -from qgis.PyQt.QtWidgets import QAction, QDialog from qgis.core import QgsSettings, QgsVectorLayer +from qgis.PyQt.QtWidgets import QAction, QDialog -from .settings_dialog import SettingsDialog from .reporting_map_tool import ReportingMapTool +from .settings_dialog import SettingsDialog class ReportingTool: @@ -13,10 +13,10 @@ def __init__(self, iface): self.layer = None self.mapTool = None s = QgsSettings() - layerUri = s.value('plugins/nnpa_reporting_plugin/layer_uri', None) - layerProvider = s.value('plugins/nnpa_reporting_plugin/layer_provider', None) - if layerUri and layerProvider: - self.setLayer(layerUri, layerProvider) + layer_uri = s.value("plugins/nnpa_reporting_plugin/layer_uri", None) + layer_provider = s.value("plugins/nnpa_reporting_plugin/layer_provider", None) + if layer_uri and layer_provider: + self.setLayer(layer_uri, layer_provider) def setLayer(self, uri, provider): self.layer = QgsVectorLayer(uri, "nnpa_reporting_layer", provider) @@ -27,7 +27,7 @@ def initGui(self): action.triggered.connect(self.run) settingsAction = QAction("Settings", self.iface.mainWindow()) settingsAction.triggered.connect(self.openSettings) - self.toolBar = self.iface.addToolBar('NNPA Reporting Tool') + self.toolBar = self.iface.addToolBar("NNPA Reporting Tool") self.toolBar.setToolTip("NNPA Reporting Toolbar") self.toolBar.setObjectName("NNPAReportingToolbar") self.toolBar.addAction(action) @@ -40,14 +40,27 @@ def unload(self): def run(self): s = QgsSettings() - name_field_name = s.value('plugins/nnpa_reporting_plugin/name_field_name', "Common_nam") - date_field_name = s.value('plugins/nnpa_reporting_plugin/date_field_name', "Sample_dat") - precision_field_name = s.value('plugins/nnpa_reporting_plugin/precision_field_name', "Precision") - if not self.layer or \ - not self.layer.isValid() or \ - self.layer.fields().indexOf(name_field_name) < 0 or \ - self.layer.fields().indexOf(date_field_name) < 0 or \ - self.layer.fields().indexOf(precision_field_name) < 0: + name_field_name = s.value("plugins/nnpa_reporting_plugin/name_field_name", "Common_nam") + date_field_name = s.value("plugins/nnpa_reporting_plugin/date_field_name", "Sample_dat") + precision_field_name = s.value("plugins/nnpa_reporting_plugin/precision_field_name", "Precision") + grid_refer_field_name = s.value("plugins/nnpa_reporting_plugin/grid_refer_field_name", "grid refer") + latin_name_field_name = s.value("plugins/nnpa_reporting_plugin/latin_name_field_name", "latin name") + recorder_field_name = s.value("plugins/nnpa_reporting_plugin/recorder_field_name", "recorder") + survey_field_name = s.value("plugins/nnpa_reporting_plugin/survey_field_name", "survey nam") + field_names = [ + name_field_name, + date_field_name, + precision_field_name, + grid_refer_field_name, + latin_name_field_name, + recorder_field_name, + survey_field_name, + ] + if ( + not self.layer + or not self.layer.isValid() + or any(self.layer.fields().indexOf(field_name) < 0 for field_name in field_names) + ): self.openSettings() return self.iface.mapCanvas().setMapTool(self.mapTool) diff --git a/NnpaReporting/settings_dialog.py b/NnpaReporting/settings_dialog.py index 5899ea1..f3946ff 100644 --- a/NnpaReporting/settings_dialog.py +++ b/NnpaReporting/settings_dialog.py @@ -1,9 +1,9 @@ import csv from os import path +from qgis.core import Qgis, QgsSettings from qgis.PyQt import uic from qgis.PyQt.QtWidgets import QDialog, QFileDialog, QMessageBox -from qgis.core import QgsSettings, Qgis from .fields_dialog import FieldsDialog @@ -22,11 +22,11 @@ def __init__(self, iface): self.ui.loadCsvButton.clicked.connect(self.load_csv) s = QgsSettings() - self.layer_uri = s.value('plugins/nnpa_reporting_plugin/layer_uri', None) - self.layer_provider = s.value('plugins/nnpa_reporting_plugin/layer_provider', None) + self.layer_uri = s.value("plugins/nnpa_reporting_plugin/layer_uri", None) + self.layer_provider = s.value("plugins/nnpa_reporting_plugin/layer_provider", None) self.ui.uriLineEdit.setText(self.layer_uri) - self.sensitive_species = s.value('plugins/nnpa_reporting_plugin/sensitive_species', []) - self.ui.sensitiveSpeciesTextEdit.setPlainText('\n'.join(self.sensitive_species)) + self.sensitive_species = s.value("plugins/nnpa_reporting_plugin/sensitive_species", []) + self.ui.sensitiveSpeciesTextEdit.setPlainText("\n".join(self.sensitive_species)) def on_pick_layer(self): layer = self.ui.layerComboBox.currentLayer() @@ -45,15 +45,13 @@ def accept(self): def save_settings(self): s = QgsSettings() - s.setValue('plugins/nnpa_reporting_plugin/layer_uri', self.layer_uri) - s.setValue('plugins/nnpa_reporting_plugin/layer_provider', self.layer_provider) - s.setValue('plugins/nnpa_reporting_plugin/sensitive_species', self.sensitive_species) + s.setValue("plugins/nnpa_reporting_plugin/layer_uri", self.layer_uri) + s.setValue("plugins/nnpa_reporting_plugin/layer_provider", self.layer_provider) + s.setValue("plugins/nnpa_reporting_plugin/sensitive_species", self.sensitive_species) def load_csv(self): """Loads CSV file and adds items to the exclusion list""" - (file_name, _) = QFileDialog.getOpenFileName( - self, "Load CSV exclusion list", "~", "CSV Files (*.csv)" - ) + (file_name, _) = QFileDialog.getOpenFileName(self, "Load CSV exclusion list", "~", "CSV Files (*.csv)") if file_name: with open(file_name, "r") as input_csv: @@ -67,4 +65,4 @@ def load_csv(self): QMessageBox.critical(self, "Error", "Field 'Common Name' was not found in the selected file") return - self.ui.sensitiveSpeciesTextEdit.setPlainText('\n'.join(self.sensitive_species)) + self.ui.sensitiveSpeciesTextEdit.setPlainText("\n".join(self.sensitive_species))