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.
301 lines
14 KiB
301 lines
14 KiB
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
|
|
|