You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
302 lines
14 KiB
302 lines
14 KiB
5 years ago
|
import os
|
||
|
import sip
|
||
|
import json
|
||
|
from typing import Tuple, Dict
|
||
|
from PyQt5.QtGui import QIntValidator, QDoubleValidator
|
||
|
from PyQt5.QtWidgets import QFrame, QVBoxLayout, QComboBox, QLineEdit, QSpacerItem, QSizePolicy, QLabel
|
||
|
|
||
|
import ManageDB
|
||
|
from Settings import SettingsModel
|
||
|
from ui import SearchTab, SearchAndFrame, SearchOrFrame
|
||
|
from Constants import *
|
||
|
from GeneralUtils import *
|
||
|
|
||
|
|
||
|
class SearchController:
|
||
|
"""Controls the Search tab
|
||
|
|
||
|
:param search_ui: the UI for the search_widget
|
||
|
:param settings: the user's settings"""
|
||
|
|
||
|
def __init__(self, search_ui: SearchTab.Ui_search_tab, settings: SettingsModel):
|
||
|
self.main_window = search_ui
|
||
|
self.settings = settings
|
||
|
|
||
|
# set up report types combobox
|
||
|
self.report_parameter = search_ui.search_report_parameter_combobox
|
||
|
self.report_parameter.addItems(ALL_REPORTS)
|
||
|
|
||
|
# set up start year dateedit
|
||
|
self.start_year_parameter = search_ui.search_start_year_parameter_dateedit
|
||
|
self.start_year_parameter.setDate(QDate.currentDate())
|
||
|
|
||
|
# set up end year dateedit
|
||
|
self.end_year_parameter = search_ui.search_end_year_parameter_dateedit
|
||
|
self.end_year_parameter.setDate(QDate.currentDate())
|
||
|
|
||
|
# set up search button
|
||
|
self.search_button = search_ui.search_button
|
||
|
self.search_button.clicked.connect(self.search)
|
||
|
|
||
|
self.open_results_file_checkbox = search_ui.search_open_file_checkbox
|
||
|
self.open_results_folder_checkbox = search_ui.search_open_folder_checkbox
|
||
|
|
||
|
# set up export button
|
||
|
self.export_button = search_ui.search_export_button
|
||
|
self.export_button.clicked.connect(self.export_parameters)
|
||
|
|
||
|
# set up import button
|
||
|
self.import_button = search_ui.search_import_button
|
||
|
self.import_button.clicked.connect(self.import_parameters)
|
||
|
|
||
|
# set up add and clause button
|
||
|
def add_and_and_or_clause():
|
||
|
"""Invoked to add an and clause containing an or clause to the search"""
|
||
|
and_clause = self.add_and_clause()
|
||
|
self.add_or_clause(and_clause)
|
||
|
self.hide_or_label_in_first_or_clause(and_clause)
|
||
|
self.hide_and_label_in_first_and_clause()
|
||
|
|
||
|
self.add_and_button = search_ui.search_add_and_button
|
||
|
self.add_and_button.clicked.connect(add_and_and_or_clause)
|
||
|
|
||
|
# resets the search clauses when the report type is changed
|
||
|
def refresh_and_add_clauses():
|
||
|
"""Resets the search clauses, then adds an and clause containing an or clause"""
|
||
|
self.refresh_clauses()
|
||
|
add_and_and_or_clause()
|
||
|
|
||
|
self.report_parameter.currentTextChanged.connect(refresh_and_add_clauses)
|
||
|
|
||
|
self.and_clause_parameters_scrollarea = search_ui.search_and_clause_parameters_scrollarea
|
||
|
self.and_clause_parameters_frame = None
|
||
|
refresh_and_add_clauses()
|
||
|
|
||
|
def update_settings(self, settings: SettingsModel):
|
||
|
"""Called when the settings are saved
|
||
|
|
||
|
:param settings: the new settings"""
|
||
|
self.settings = settings
|
||
|
|
||
|
def refresh_clauses(self):
|
||
|
"""Resets the search clauses"""
|
||
|
self.and_clause_parameters_frame = QFrame()
|
||
|
self.and_clause_parameters_frame.setLayout(QVBoxLayout())
|
||
|
self.and_clause_parameters_frame.layout().addItem(QSpacerItem(0, 0, QSizePolicy.Expanding,
|
||
|
QSizePolicy.Expanding))
|
||
|
self.and_clause_parameters_scrollarea.setWidget(self.and_clause_parameters_frame)
|
||
|
|
||
|
def add_and_clause(self) -> SearchAndFrame.Ui_search_and_clause_parameter_frame:
|
||
|
"""Adds an and clause to the search"""
|
||
|
and_clause = QFrame()
|
||
|
and_clause_ui = SearchAndFrame.Ui_search_and_clause_parameter_frame()
|
||
|
and_clause_ui.setupUi(and_clause)
|
||
|
|
||
|
# set up add or clause button
|
||
|
def add_or_to_this_and():
|
||
|
"""Adds an or clause to this and clause"""
|
||
|
self.add_or_clause(and_clause_ui)
|
||
|
self.hide_or_label_in_first_or_clause(and_clause_ui)
|
||
|
|
||
|
and_clause_ui.search_add_or_clause_button.clicked.connect(add_or_to_this_and)
|
||
|
|
||
|
# set up remove current and clause button
|
||
|
def remove_this_and():
|
||
|
"""Removes this and clause"""
|
||
|
self.and_clause_parameters_frame.layout().removeWidget(and_clause)
|
||
|
sip.delete(and_clause)
|
||
|
self.hide_and_label_in_first_and_clause()
|
||
|
self.and_clause_parameters_frame.repaint()
|
||
|
|
||
|
and_clause_ui.search_remove_and_clause_button.clicked.connect(remove_this_and)
|
||
|
|
||
|
# add to the layout
|
||
|
self.and_clause_parameters_frame.layout().insertWidget(self.and_clause_parameters_frame.layout().count() - 1,
|
||
|
and_clause)
|
||
|
|
||
|
return and_clause_ui
|
||
|
|
||
|
def hide_and_label_in_first_and_clause(self):
|
||
|
"""Hides the and label for the first and clause in the search"""
|
||
|
and_clause = self.and_clause_parameters_frame.findChild(QFrame, 'search_and_clause_parameter_frame')
|
||
|
if and_clause:
|
||
|
and_clause.findChild(QLabel, "search_and_label").hide()
|
||
|
|
||
|
def add_or_clause(self, and_clause: SearchAndFrame.Ui_search_and_clause_parameter_frame) \
|
||
|
-> SearchOrFrame.Ui_search_or_clause_parameter_frame:
|
||
|
"""Adds an or clause to the search
|
||
|
|
||
|
:param and_clause: the and clause the or clause is added to"""
|
||
|
or_clause = QFrame()
|
||
|
or_clause_ui = SearchOrFrame.Ui_search_or_clause_parameter_frame()
|
||
|
or_clause_ui.setupUi(or_clause)
|
||
|
|
||
|
# fill field combobox
|
||
|
field_combobox = or_clause_ui.search_field_parameter_combobox
|
||
|
for field in ManageDB.get_view_report_fields_list(self.report_parameter.currentText()):
|
||
|
if field[NAME_KEY] not in FIELDS_NOT_IN_SEARCH_DROPDOWN:
|
||
|
field_combobox.addItem(field[NAME_KEY], field['type'])
|
||
|
|
||
|
type_label = or_clause_ui.search_type_label
|
||
|
|
||
|
value_lineedit = or_clause_ui.search_value_parameter_lineedit
|
||
|
|
||
|
def on_field_changed():
|
||
|
"""Invoked when the field parameter is changed"""
|
||
|
type_label.setText(field_combobox.currentData().capitalize() + " Input")
|
||
|
value_lineedit.setText(None)
|
||
|
if field_combobox.currentData() == 'INTEGER':
|
||
|
value_lineedit.setValidator(QIntValidator())
|
||
|
elif field_combobox.currentData() == 'REAL':
|
||
|
value_lineedit.setValidator(QDoubleValidator())
|
||
|
else:
|
||
|
value_lineedit.setValidator(None)
|
||
|
|
||
|
field_combobox.currentTextChanged.connect(on_field_changed)
|
||
|
on_field_changed()
|
||
|
|
||
|
# fill comparison operator combobox
|
||
|
comparison_combobox = or_clause_ui.search_comparison_parameter_combobox
|
||
|
comparison_combobox.addItems(COMPARISON_OPERATORS)
|
||
|
comparison_combobox.addItems(NON_COMPARISONS)
|
||
|
|
||
|
def on_comparison_changed():
|
||
|
"""Invoked when the comparison parameter is changed"""
|
||
|
if comparison_combobox.currentText() in NON_COMPARISONS:
|
||
|
value_lineedit.setText(None)
|
||
|
value_lineedit.setEnabled(False)
|
||
|
else:
|
||
|
value_lineedit.setEnabled(True)
|
||
|
|
||
|
comparison_combobox.currentTextChanged.connect(on_comparison_changed)
|
||
|
|
||
|
# set up remove current or clause button
|
||
|
def remove_this_or():
|
||
|
"""Removes this or clause"""
|
||
|
and_clause.search_or_clause_parameters_frame.layout().removeWidget(or_clause)
|
||
|
sip.delete(or_clause)
|
||
|
self.hide_or_label_in_first_or_clause(and_clause)
|
||
|
and_clause.search_or_clause_parameters_frame.repaint()
|
||
|
|
||
|
or_clause_ui.search_remove_or_clause_button.clicked.connect(remove_this_or)
|
||
|
|
||
|
# add to parent and clause's layout
|
||
|
and_clause.search_or_clause_parameters_frame.layout().addWidget(or_clause)
|
||
|
|
||
|
return or_clause_ui
|
||
|
|
||
|
def hide_or_label_in_first_or_clause(self, and_clause: SearchAndFrame.Ui_search_and_clause_parameter_frame):
|
||
|
"""Hides the or label for the first or clause in an and clause
|
||
|
|
||
|
:param and_clause: the and clause"""
|
||
|
or_clause = and_clause.search_or_clause_parameters_frame.findChild(QFrame, 'search_or_clause_parameter_frame')
|
||
|
if or_clause:
|
||
|
or_clause.findChild(QLabel, "search_or_label").hide()
|
||
|
|
||
|
def export_parameters(self):
|
||
|
"""Exports the current search parameters to the selected file"""
|
||
|
file_name = choose_save(JSON_FILTER)
|
||
|
if file_name != '':
|
||
|
if not file_name.lower().endswith('.dat'):
|
||
|
file_name += '.dat'
|
||
|
report, start_year, end_year, search_parameters = self.get_search_parameters()
|
||
|
file = open(file_name, 'w', encoding='utf-8-sig')
|
||
|
if file.mode == 'w':
|
||
|
json.dump({'report': report, 'start_year': start_year, 'end_year': end_year,
|
||
|
'search_parameters': search_parameters}, file)
|
||
|
show_message('Search saved to ' + file_name)
|
||
|
else:
|
||
|
print('Error, no file location selected')
|
||
|
|
||
|
def import_parameters(self):
|
||
|
"""Imports a new set of search parameters from the selected file"""
|
||
|
file_name = choose_file(JSON_FILTER)
|
||
|
if file_name != '':
|
||
|
fields = json.loads(read_json_file(file_name))
|
||
|
self.report_parameter.setCurrentText(fields['report'])
|
||
|
self.start_year_parameter.setDate(QDate(fields['start_year'], 1, 1))
|
||
|
self.end_year_parameter.setDate(QDate(fields['end_year'], 1, 1))
|
||
|
clauses = fields['search_parameters']
|
||
|
self.refresh_clauses()
|
||
|
for clause in clauses:
|
||
|
and_clause = self.add_and_clause()
|
||
|
for sub_clause in clause:
|
||
|
or_clause = self.add_or_clause(and_clause)
|
||
|
or_clause.search_field_parameter_combobox.setCurrentText(sub_clause[FIELD_KEY])
|
||
|
or_clause.search_comparison_parameter_combobox.setCurrentText(sub_clause[COMPARISON_KEY])
|
||
|
or_clause.search_value_parameter_lineedit.setText(str(sub_clause[VALUE_KEY]))
|
||
|
|
||
|
def search(self):
|
||
|
"""Queries the database based on the current search parameters and saves the results to the selected file"""
|
||
|
report, start_year, end_year, search_parameters = self.get_search_parameters()
|
||
|
|
||
|
# sql query to get search results
|
||
|
sql_text, data = ManageDB.search_sql_text(report, start_year, end_year, search_parameters)
|
||
|
|
||
|
headers = []
|
||
|
for field in ManageDB.get_view_report_fields_list(report):
|
||
|
headers.append(field[NAME_KEY])
|
||
|
|
||
|
file_name = choose_save(TSV_FILTER)
|
||
|
if file_name != '':
|
||
|
if not file_name.lower().endswith('.tsv'):
|
||
|
file_name += '.tsv'
|
||
|
connection = ManageDB.create_connection(DATABASE_LOCATION)
|
||
|
if connection is not None:
|
||
|
results = ManageDB.run_select_sql(connection, sql_text, data)
|
||
|
connection.close()
|
||
|
results.insert(0, headers)
|
||
|
if self.settings.show_debug_messages: print(results)
|
||
|
save_data_as_tsv(file_name, results)
|
||
|
if self.open_results_folder_checkbox.isChecked():
|
||
|
open_file_or_dir(os.path.dirname(file_name))
|
||
|
if self.open_results_file_checkbox.isChecked():
|
||
|
open_file_or_dir(file_name)
|
||
|
if not self.open_results_file_checkbox.isChecked():
|
||
|
show_message('Results saved to ' + file_name)
|
||
|
else:
|
||
|
print('Error, no connection')
|
||
|
else:
|
||
|
print('Error, no file location selected')
|
||
|
|
||
|
def get_search_parameters(self) -> Tuple[str, int, int, Sequence[Sequence[Dict[str, Any]]]]:
|
||
|
"""Reads the current search parameters from the UI
|
||
|
|
||
|
:returns: (report, start_year, end_year, search_parameters) a Tuple with the kind of report selected, the
|
||
|
starting year selected, the ending year selected, and a list of the search parameters in POS form (and of
|
||
|
ors)"""
|
||
|
# get report type
|
||
|
report = self.report_parameter.currentText()
|
||
|
# get start year
|
||
|
start_year = int(self.start_year_parameter.text())
|
||
|
# get end year
|
||
|
end_year = int(self.end_year_parameter.text())
|
||
|
|
||
|
search_parameters = []
|
||
|
for and_widget in self.and_clause_parameters_frame.findChildren(QFrame, 'search_and_clause_parameter_frame'):
|
||
|
# iterate over and clauses
|
||
|
or_clause_parameters = and_widget.findChild(QFrame, 'search_or_clause_parameters_frame')
|
||
|
or_clauses = []
|
||
|
for or_widget in or_clause_parameters.findChildren(QFrame, 'search_or_clause_parameter_frame'):
|
||
|
# iterate over child or clauses
|
||
|
# get parameters for clause
|
||
|
field_parameter_combobox = or_widget.findChild(QComboBox, 'search_field_parameter_combobox')
|
||
|
field_parameter = field_parameter_combobox.currentText()
|
||
|
comparison_parameter_combobox = or_widget.findChild(QComboBox, 'search_comparison_parameter_combobox')
|
||
|
comparison_parameter = comparison_parameter_combobox.currentText()
|
||
|
value_parameter_lineedit = or_widget.findChild(QLineEdit, 'search_value_parameter_lineedit')
|
||
|
value_parameter = None
|
||
|
if comparison_parameter in NON_COMPARISONS:
|
||
|
pass
|
||
|
elif field_parameter_combobox.currentData() == 'INTEGER':
|
||
|
value_parameter = int(value_parameter_lineedit.text())
|
||
|
elif field_parameter_combobox.currentData() == 'REAL':
|
||
|
value_parameter = float(value_parameter_lineedit.text())
|
||
|
else:
|
||
|
value_parameter = value_parameter_lineedit.text()
|
||
|
or_clauses.append(
|
||
|
{FIELD_KEY: field_parameter, COMPARISON_KEY: comparison_parameter, VALUE_KEY: value_parameter})
|
||
|
search_parameters.append(or_clauses)
|
||
|
|
||
|
return report, start_year, end_year, search_parameters
|