Adam
5 years ago
commit
9b322cab8a
1272 changed files with 1590443 additions and 0 deletions
@ -0,0 +1,25 @@ |
|||||||
|
import os |
||||||
|
|
||||||
|
|
||||||
|
def save_json_file(file_dir: str, file_name: str, json_string: str): |
||||||
|
try: |
||||||
|
if not os.path.isdir(file_dir): |
||||||
|
os.makedirs(file_dir) |
||||||
|
file = open(file_dir + file_name, 'w') |
||||||
|
file.write(json_string) |
||||||
|
file.close() |
||||||
|
except IOError as e: |
||||||
|
print(e) |
||||||
|
|
||||||
|
|
||||||
|
def read_json_file(file_path: str): |
||||||
|
json_string = "[]" |
||||||
|
try: |
||||||
|
file = open(file_path, 'r') |
||||||
|
json_string = file.read() |
||||||
|
file.close() |
||||||
|
except IOError as e: |
||||||
|
print(e) |
||||||
|
finally: |
||||||
|
return json_string |
||||||
|
|
@ -0,0 +1,212 @@ |
|||||||
|
import shutil |
||||||
|
import webbrowser |
||||||
|
from os import path, makedirs, system |
||||||
|
from PyQt5.QtCore import QModelIndex, QDate, Qt |
||||||
|
from PyQt5.QtWidgets import QWidget, QDialog, QFileDialog |
||||||
|
from PyQt5.QtGui import QStandardItemModel, QStandardItem, QIcon, QPixmap |
||||||
|
from PyQt5 import QtWidgets |
||||||
|
from ui import MainWindow, MessageDialog, ReportResultWidget |
||||||
|
from ManageVendors import Vendor |
||||||
|
from FetchData import REPORT_TYPES, CompletionStatus |
||||||
|
from Settings import SettingsModel |
||||||
|
import platform |
||||||
|
import shlex |
||||||
|
|
||||||
|
|
||||||
|
class ProcessResult: |
||||||
|
def __init__(self, vendor: Vendor, report_type: str): |
||||||
|
self.vendor = vendor |
||||||
|
self.report_type = report_type |
||||||
|
self.completion_status = CompletionStatus.SUCCESSFUL |
||||||
|
self.message = "" |
||||||
|
self.file_name = "" |
||||||
|
self.file_dir = "" |
||||||
|
self.file_path = "" |
||||||
|
|
||||||
|
|
||||||
|
class ImportFileController: |
||||||
|
def __init__(self, vendors: list, settings: SettingsModel, main_window_ui: MainWindow.Ui_mainWindow): |
||||||
|
|
||||||
|
# region General |
||||||
|
self.vendors = vendors |
||||||
|
self.date = QDate.currentDate() |
||||||
|
self.selected_vendor_index = -1 |
||||||
|
self.selected_report_type_index = -1 |
||||||
|
self.selected_file_name: str = "" |
||||||
|
self.selected_file_path: str = "" |
||||||
|
self.settings = settings |
||||||
|
self.result_dialog = None |
||||||
|
# endregion |
||||||
|
|
||||||
|
# region Vendors |
||||||
|
self.vendor_list_view = main_window_ui.vendors_list_view_import |
||||||
|
self.vendor_list_model = QStandardItemModel(self.vendor_list_view) |
||||||
|
self.vendor_list_view.setModel(self.vendor_list_model) |
||||||
|
self.vendor_list_view.clicked.connect(self.on_vendor_selected) |
||||||
|
self.update_vendors_ui() |
||||||
|
# endregion |
||||||
|
|
||||||
|
# region Report Types |
||||||
|
self.report_type_list_view = main_window_ui.report_types_list_view_import |
||||||
|
self.report_type_list_model = QStandardItemModel(self.report_type_list_view) |
||||||
|
self.report_type_list_view.setModel(self.report_type_list_model) |
||||||
|
self.report_type_list_view.clicked.connect(self.on_report_type_selected) |
||||||
|
for report_type in REPORT_TYPES: |
||||||
|
item = QStandardItem(report_type) |
||||||
|
item.setEditable(False) |
||||||
|
self.report_type_list_model.appendRow(item) |
||||||
|
# endregion |
||||||
|
|
||||||
|
# region Others |
||||||
|
self.year_date_edit = main_window_ui.report_year_date_edit |
||||||
|
self.year_date_edit.setDate(self.date) |
||||||
|
self.year_date_edit.dateChanged.connect(self.on_date_changed) |
||||||
|
|
||||||
|
self.select_file_btn = main_window_ui.select_file_button |
||||||
|
self.select_file_btn.clicked.connect(self.open_file_select_dialog) |
||||||
|
|
||||||
|
self.selected_file_label = main_window_ui.selected_file_label |
||||||
|
|
||||||
|
self.import_file_button = main_window_ui.import_file_button |
||||||
|
self.import_file_button.clicked.connect(self.on_import_clicked) |
||||||
|
# endregion |
||||||
|
|
||||||
|
def on_vendors_changed(self, vendors: list): |
||||||
|
self.selected_vendor_index = -1 |
||||||
|
self.update_vendors(vendors) |
||||||
|
self.update_vendors_ui() |
||||||
|
|
||||||
|
def update_vendors(self, vendors: list): |
||||||
|
self.vendors = vendors |
||||||
|
|
||||||
|
def update_vendors_ui(self): |
||||||
|
self.vendor_list_model.clear() |
||||||
|
for vendor in self.vendors: |
||||||
|
item = QStandardItem(vendor.name) |
||||||
|
item.setEditable(False) |
||||||
|
self.vendor_list_model.appendRow(item) |
||||||
|
|
||||||
|
def on_vendor_selected(self, model_index: QModelIndex): |
||||||
|
self.selected_vendor_index = model_index.row() |
||||||
|
|
||||||
|
def on_report_type_selected(self, model_index: QModelIndex): |
||||||
|
self.selected_report_type_index = model_index.row() |
||||||
|
|
||||||
|
def on_date_changed(self, date: QDate): |
||||||
|
self.date = date |
||||||
|
|
||||||
|
def open_file_select_dialog(self): |
||||||
|
dialog = QFileDialog() |
||||||
|
dialog.setFileMode(QFileDialog.ExistingFile) |
||||||
|
dialog.setNameFilter("All TSV files (*.tsv)") |
||||||
|
if dialog.exec_(): |
||||||
|
self.selected_file_path = dialog.selectedFiles()[0] |
||||||
|
arr = self.selected_file_path.split("/") |
||||||
|
self.selected_file_name = arr[max(len(arr), 1) - 1] |
||||||
|
self.selected_file_label.setText(self.selected_file_name) |
||||||
|
|
||||||
|
def on_import_clicked(self): |
||||||
|
if self.selected_vendor_index == -1: |
||||||
|
self.show_message("Select a vendor") |
||||||
|
return |
||||||
|
elif self.selected_report_type_index == -1: |
||||||
|
self.show_message("Select a report type") |
||||||
|
return |
||||||
|
elif self.selected_file_path == "": |
||||||
|
self.show_message("Select a file") |
||||||
|
return |
||||||
|
|
||||||
|
vendor = self.vendors[self.selected_vendor_index] |
||||||
|
report_type = REPORT_TYPES[self.selected_report_type_index] |
||||||
|
|
||||||
|
process_result = self.import_file(vendor, report_type) |
||||||
|
self.show_result(process_result) |
||||||
|
|
||||||
|
def import_file(self, vendor: Vendor, report_type: str) -> ProcessResult: |
||||||
|
process_result = ProcessResult(vendor, report_type) |
||||||
|
|
||||||
|
try: |
||||||
|
dest_file_dir = f"{self.settings.yearly_directory}{self.date.toString('yyyy')}/{vendor.name}/" |
||||||
|
dest_file_name = f"{self.date.toString('yyyy')}_{vendor.name}_{report_type}.tsv" |
||||||
|
dest_file_path = f"{dest_file_dir}{dest_file_name}" |
||||||
|
|
||||||
|
self.verify_path_exists(dest_file_dir) |
||||||
|
self.copy_file(self.selected_file_path, dest_file_path) |
||||||
|
|
||||||
|
process_result.file_dir = dest_file_dir |
||||||
|
process_result.file_name = dest_file_name |
||||||
|
process_result.file_path = dest_file_path |
||||||
|
process_result.completion_status = CompletionStatus.SUCCESSFUL |
||||||
|
|
||||||
|
except Exception as e: |
||||||
|
process_result.message = f"Exception: {e}" |
||||||
|
process_result.completion_status = CompletionStatus.FAILED |
||||||
|
|
||||||
|
return process_result |
||||||
|
|
||||||
|
def verify_path_exists(self, path_str: str): |
||||||
|
if not path.isdir(path_str): |
||||||
|
makedirs(path_str) |
||||||
|
|
||||||
|
def copy_file(self, origin_path: str, dest_path: str): |
||||||
|
shutil.copy2(origin_path, dest_path) |
||||||
|
|
||||||
|
def show_result(self, process_result: ProcessResult): |
||||||
|
self.result_dialog = QDialog(flags=Qt.WindowCloseButtonHint) |
||||||
|
self.result_dialog.setWindowTitle("Import Result") |
||||||
|
vertical_layout = QtWidgets.QVBoxLayout(self.result_dialog) |
||||||
|
vertical_layout.setContentsMargins(5, 5, 5, 5) |
||||||
|
|
||||||
|
report_result_widget = QWidget(self.result_dialog) |
||||||
|
report_result_ui = ReportResultWidget.Ui_ReportResultWidget() |
||||||
|
report_result_ui.setupUi(report_result_widget) |
||||||
|
|
||||||
|
vendor = process_result.vendor |
||||||
|
report_type = process_result.report_type |
||||||
|
|
||||||
|
report_result_ui.report_type_label.setText(f"{vendor.name} - {report_type}") |
||||||
|
report_result_ui.success_label.setText(process_result.completion_status.value) |
||||||
|
|
||||||
|
if process_result.completion_status == CompletionStatus.SUCCESSFUL: |
||||||
|
report_result_ui.message_label.hide() |
||||||
|
report_result_ui.retry_frame.hide() |
||||||
|
|
||||||
|
report_result_ui.file_label.setText(f"Saved as: {process_result.file_name}") |
||||||
|
report_result_ui.file_label.mousePressEvent = lambda event: self.open_explorer(process_result.file_path) |
||||||
|
|
||||||
|
folder_pixmap = QPixmap("./ui/resources/folder_icon.png") |
||||||
|
report_result_ui.folder_button.setIcon(QIcon(folder_pixmap)) |
||||||
|
report_result_ui.folder_button.clicked.connect(lambda: self.open_explorer(process_result.file_dir)) |
||||||
|
|
||||||
|
report_result_ui.success_label.setText("Successful!") |
||||||
|
report_result_ui.retry_frame.hide() |
||||||
|
|
||||||
|
elif process_result.completion_status == CompletionStatus.FAILED: |
||||||
|
report_result_ui.file_frame.hide() |
||||||
|
report_result_ui.retry_frame.hide() |
||||||
|
|
||||||
|
report_result_ui.message_label.setText(process_result.message) |
||||||
|
|
||||||
|
vertical_layout.addWidget(report_result_widget) |
||||||
|
self.result_dialog.show() |
||||||
|
|
||||||
|
def show_message(self, message: str): |
||||||
|
message_dialog = QDialog(flags=Qt.WindowCloseButtonHint) |
||||||
|
message_dialog_ui = MessageDialog.Ui_message_dialog() |
||||||
|
message_dialog_ui.setupUi(message_dialog) |
||||||
|
|
||||||
|
message_label = message_dialog_ui.message_label |
||||||
|
message_label.setText(message) |
||||||
|
|
||||||
|
message_dialog.show() |
||||||
|
|
||||||
|
def open_explorer(self, file_path: str): |
||||||
|
if path.exists(file_path): |
||||||
|
if(platform.system()=="Windows"): |
||||||
|
webbrowser.open(path.realpath(file_path)) |
||||||
|
elif(platform.system()=="Darwin"): |
||||||
|
system("open " + shlex.quote(file_path)) |
||||||
|
else: |
||||||
|
self.show_message(f"\'{file_path}\' does not exist") |
||||||
|
|
||||||
|
|
@ -0,0 +1,3 @@ |
|||||||
|
class JsonModel: |
||||||
|
def from_json(self, json_dict: dict): |
||||||
|
raise NotImplementedError("from_json method is not implemented") |
@ -0,0 +1,56 @@ |
|||||||
|
import sys |
||||||
|
from PyQt5.QtCore import Qt |
||||||
|
from PyQt5.QtWidgets import QApplication, QMainWindow |
||||||
|
import ui.MainWindow |
||||||
|
from ManageVendors import ManageVendorsController |
||||||
|
from FetchData import FetchReportsController, FetchSpecialReportsController |
||||||
|
from ImportFile import ImportFileController |
||||||
|
from Search import SearchController |
||||||
|
from Settings import SettingsController |
||||||
|
|
||||||
|
|
||||||
|
# region debug_stuff |
||||||
|
|
||||||
|
def trap_exc_during_debug(*args): |
||||||
|
# when app raises uncaught exception, print info |
||||||
|
print(args) |
||||||
|
|
||||||
|
|
||||||
|
# install exception hook: without this, uncaught exception would cause application to exit |
||||||
|
sys.excepthook = trap_exc_during_debug |
||||||
|
|
||||||
|
# endregion |
||||||
|
|
||||||
|
if hasattr(Qt, 'AA_EnableHighDpiScaling'): |
||||||
|
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True) |
||||||
|
|
||||||
|
if hasattr(Qt, 'AA_UseHighDpiPixmaps'): |
||||||
|
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True) |
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
app = QApplication(sys.argv) |
||||||
|
|
||||||
|
main_window = QMainWindow() |
||||||
|
main_window_ui = ui.MainWindow.Ui_mainWindow() |
||||||
|
main_window_ui.setupUi(main_window) |
||||||
|
|
||||||
|
# region Setup Tab Controllers |
||||||
|
manage_vendors_controller = ManageVendorsController(main_window_ui) |
||||||
|
settings_controller = SettingsController(main_window_ui) |
||||||
|
fetch_reports_controller = FetchReportsController(manage_vendors_controller.vendors, settings_controller.settings, |
||||||
|
main_window_ui) |
||||||
|
fetch_special_reports_controller = FetchSpecialReportsController(manage_vendors_controller.vendors, |
||||||
|
settings_controller.settings, main_window_ui) |
||||||
|
search_controller = SearchController(main_window_ui) |
||||||
|
import_file_controller = ImportFileController(manage_vendors_controller.vendors, settings_controller.settings, |
||||||
|
main_window_ui) |
||||||
|
# endregion |
||||||
|
|
||||||
|
# region Connect Signals |
||||||
|
manage_vendors_controller.vendors_changed_signal.connect(fetch_reports_controller.on_vendors_changed) |
||||||
|
manage_vendors_controller.vendors_changed_signal.connect(fetch_special_reports_controller.on_vendors_changed) |
||||||
|
manage_vendors_controller.vendors_changed_signal.connect(import_file_controller.on_vendors_changed) |
||||||
|
# endregion |
||||||
|
|
||||||
|
main_window.show() |
||||||
|
sys.exit(app.exec_()) |
@ -0,0 +1,593 @@ |
|||||||
|
import sqlite3 |
||||||
|
import os |
||||||
|
import csv |
||||||
|
|
||||||
|
# database report definitions |
||||||
|
DATABASE_REPORTS = ('DR', 'DR_D1', 'DR_D2') |
||||||
|
DATABASE_REPORT_FIELDS = ({'name': 'database', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL', 'CHECK(database <> \"\")'), |
||||||
|
'reports': ('DR', 'DR_D1', 'DR_D2')}, |
||||||
|
{'name': 'publisher', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('DR', 'DR_D1', 'DR_D2')}, |
||||||
|
{'name': 'publisher_id', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('DR', 'DR_D1', 'DR_D2')}, |
||||||
|
{'name': 'platform', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('DR', 'DR_D1', 'DR_D2')}, |
||||||
|
{'name': 'proprietary_id', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('DR', 'DR_D1', 'DR_D2')}, |
||||||
|
{'name': 'data_type', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('DR',)}, |
||||||
|
{'name': 'access_method', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('DR',)}) |
||||||
|
|
||||||
|
# item report definitions |
||||||
|
ITEM_REPORTS = ('IR', 'IR_A1', 'IR_M1') |
||||||
|
ITEM_REPORT_FIELDS = ({'name': 'item', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR', 'IR_A1', 'IR_M1')}, |
||||||
|
{'name': 'publisher', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR', 'IR_A1', 'IR_M1')}, |
||||||
|
{'name': 'publisher_id', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR', 'IR_A1', 'IR_M1')}, |
||||||
|
{'name': 'platform', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR', 'IR_A1', 'IR_M1')}, |
||||||
|
{'name': 'authors', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR', 'IR_A1')}, |
||||||
|
{'name': 'publication_date', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR', 'IR_A1')}, |
||||||
|
{'name': 'doi', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR', 'IR_A1', 'IR_M1')}, |
||||||
|
{'name': 'proprietary_id', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR', 'IR_A1', 'IR_M1')}, |
||||||
|
{'name': 'isbn', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR',)}, |
||||||
|
{'name': 'print_issn', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR', 'IR_A1')}, |
||||||
|
{'name': 'online_issn', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR', 'IR_A1')}, |
||||||
|
{'name': 'uri', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR', 'IR_A1', 'IR_M1')}, |
||||||
|
{'name': 'parent_title', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR', 'IR_A1')}, |
||||||
|
{'name': 'parent_authors', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR', 'IR_A1')}, |
||||||
|
{'name': 'parent_publication_date', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR',)}, |
||||||
|
{'name': 'parent_article_version', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR', 'IR_A1')}, |
||||||
|
{'name': 'parent_data_type', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR',)}, |
||||||
|
{'name': 'parent_doi', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR', 'IR_A1')}, |
||||||
|
{'name': 'parent_proprietary_id', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR', 'IR_A1')}, |
||||||
|
{'name': 'parent_isbn', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR',)}, |
||||||
|
{'name': 'parent_print_issn', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR', 'IR_A1')}, |
||||||
|
{'name': 'parent_online_issn', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR', 'IR_A1')}, |
||||||
|
{'name': 'parent_uri', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR', 'IR_A1')}, |
||||||
|
{'name': 'component_title', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR',)}, |
||||||
|
{'name': 'component_authors', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR',)}, |
||||||
|
{'name': 'component_publication_date', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR',)}, |
||||||
|
{'name': 'component_data_type', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR',)}, |
||||||
|
{'name': 'component_doi', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR',)}, |
||||||
|
{'name': 'component_proprietary_id', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR',)}, |
||||||
|
{'name': 'component_isbn', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR',)}, |
||||||
|
{'name': 'component_print_issn', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR',)}, |
||||||
|
{'name': 'component_online_issn', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR',)}, |
||||||
|
{'name': 'component_uri', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR',)}, |
||||||
|
{'name': 'data_type', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR',)}, |
||||||
|
{'name': 'yop', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR',)}, |
||||||
|
{'name': 'access_type', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR', 'IR_A1')}, |
||||||
|
{'name': 'access_method', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('IR',)}) |
||||||
|
|
||||||
|
# platform report definitions |
||||||
|
PLATFORM_REPORTS = ('PR', 'PR_P1') |
||||||
|
PLATFORM_REPORT_FIELDS = ({'name': 'platform', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('PR', 'PR_P1')}, |
||||||
|
{'name': 'data_type', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('PR',)}, |
||||||
|
{'name': 'access_type', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('PR',)}) |
||||||
|
|
||||||
|
# title report definitions |
||||||
|
TITLE_REPORTS = ('TR', 'TR_B1', 'TR_B2', 'TR_B3', 'TR_J1', 'TR_J2', 'TR_J3', 'TR_J4') |
||||||
|
TITLE_REPORT_FIELDS = ({'name': 'title', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL', 'CHECK(title <> \"\")'), |
||||||
|
'reports': ('TR', 'TR_B1', 'TR_B2', 'TR_B3', 'TR_J1', 'TR_J2', 'TR_J3', 'TR_J4')}, |
||||||
|
{'name': 'publisher', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('TR', 'TR_B1', 'TR_B2', 'TR_B3', 'TR_J1', 'TR_J2', 'TR_J3', 'TR_J4')}, |
||||||
|
{'name': 'publisher_id', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('TR', 'TR_B1', 'TR_B2', 'TR_B3', 'TR_J1', 'TR_J2', 'TR_J3', 'TR_J4')}, |
||||||
|
{'name': 'platform', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('TR', 'TR_B1', 'TR_B2', 'TR_B3', 'TR_J1', 'TR_J2', 'TR_J3', 'TR_J4')}, |
||||||
|
{'name': 'doi', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('TR', 'TR_B1', 'TR_B2', 'TR_B3', 'TR_J1', 'TR_J2', 'TR_J3', 'TR_J4')}, |
||||||
|
{'name': 'proprietary_id', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('TR', 'TR_B1', 'TR_B2', 'TR_B3', 'TR_J1', 'TR_J2', 'TR_J3', 'TR_J4')}, |
||||||
|
{'name': 'isbn', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('TR', 'TR_B1', 'TR_B2', 'TR_B3')}, |
||||||
|
{'name': 'print_issn', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('TR', 'TR_B1', 'TR_B2', 'TR_B3', 'TR_J1', 'TR_J2', 'TR_J3', 'TR_J4')}, |
||||||
|
{'name': 'online_issn', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('TR', 'TR_B1', 'TR_B2', 'TR_B3', 'TR_J1', 'TR_J2', 'TR_J3', 'TR_J4')}, |
||||||
|
{'name': 'uri', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('TR', 'TR_B1', 'TR_B2', 'TR_B3', 'TR_J1', 'TR_J2', 'TR_J3', 'TR_J4')}, |
||||||
|
{'name': 'data_type', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('TR',)}, |
||||||
|
{'name': 'section_type', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('TR',)}, |
||||||
|
{'name': 'yop', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('TR', 'TR_B1', 'TR_B2', 'TR_B3', 'TR_J4')}, |
||||||
|
{'name': 'access_type', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('TR', 'TR_B3', 'TR_J3')}, |
||||||
|
{'name': 'access_method', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',), |
||||||
|
'reports': ('TR',)}) |
||||||
|
|
||||||
|
# fields that all reports have |
||||||
|
ALL_REPORT_FIELDS = ({'name': 'metric_type', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL', 'CHECK(metric_type <> \"\")')}, |
||||||
|
{'name': 'vendor', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL', 'CHECK(vendor <> \"\")')}, |
||||||
|
{'name': 'year', |
||||||
|
'type': 'INTEGER', |
||||||
|
'options': ('NOT NULL', 'CHECK(LENGTH(year) = 4)')}, |
||||||
|
{'name': 'month', |
||||||
|
'type': 'INTEGER', |
||||||
|
'options': ('NOT NULL', 'CHECK(month BETWEEN 1 AND 12)')}, |
||||||
|
{'name': 'metric', |
||||||
|
'type': 'INTEGER', |
||||||
|
'options': ('NOT NULL', 'CHECK(metric > 0)')}, |
||||||
|
{'name': 'updated_on', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',)}, |
||||||
|
{'name': 'file', |
||||||
|
'type': 'TEXT', |
||||||
|
'options': ('NOT NULL',)}) |
||||||
|
|
||||||
|
# TODO add cost tables |
||||||
|
|
||||||
|
REPORT_TYPE_SWITCHER = {'DR': {'reports': DATABASE_REPORTS, 'report_fields': DATABASE_REPORT_FIELDS}, |
||||||
|
'IR': {'reports': ITEM_REPORTS, 'report_fields': ITEM_REPORT_FIELDS}, |
||||||
|
'PR': {'reports': PLATFORM_REPORTS, 'report_fields': PLATFORM_REPORT_FIELDS}, |
||||||
|
'TR': {'reports': TITLE_REPORTS, 'report_fields': TITLE_REPORT_FIELDS}} |
||||||
|
|
||||||
|
MONTHS = {1: 'january', 2: 'february', 3: 'march', 4: 'april', 5: 'may', 6: 'june', |
||||||
|
7: 'july', 8: 'august', 9: 'september', 10: 'october', 11: 'november', 12: 'december'} |
||||||
|
|
||||||
|
YEAR_TOTAL = 'reporting_period_total' |
||||||
|
|
||||||
|
VIEW_SUFFIX = '_view' |
||||||
|
|
||||||
|
FIELDS_NOT_IN_VIEWS = ('month', 'metric', 'updated_on') |
||||||
|
FIELDS_NOT_IN_KEYS = ('metric', 'updated_on') |
||||||
|
FIELDS_NOT_IN_SEARCH = ('year',) |
||||||
|
|
||||||
|
DATABASE_FOLDER = r'./all_data/search/' |
||||||
|
DATABASE_LOCATION = DATABASE_FOLDER + r'search.db' |
||||||
|
FILE_LOCATION = r'./all_data/yearly_files/' |
||||||
|
FILE_SUBDIRECTORY_ORDER = ('year', 'vendor') |
||||||
|
|
||||||
|
HEADER_ROWS = 12 |
||||||
|
BLANK_ROWS = 1 |
||||||
|
DELIMITERS = {'.tsv': '\t', '.csv': ','} |
||||||
|
|
||||||
|
|
||||||
|
def get_report_fields_list(report, is_view): |
||||||
|
report_fields = REPORT_TYPE_SWITCHER[report[:2]]['report_fields'] |
||||||
|
fields = [] |
||||||
|
for field in report_fields: # fields specific to this report |
||||||
|
if report in field['reports']: |
||||||
|
fields.append({'name': field['name'], 'type': field['type'], 'options': field['options']}) |
||||||
|
for field in ALL_REPORT_FIELDS: # fields in all reports |
||||||
|
if not is_view or field['name'] not in FIELDS_NOT_IN_VIEWS: |
||||||
|
fields.append({'name': field['name'], 'type': field['type'], 'options': field['options']}) |
||||||
|
if is_view: |
||||||
|
# year total column |
||||||
|
fields.append({'name': YEAR_TOTAL, 'type': 'INTEGER', 'calculation': 'SUM(' + 'metric' + ')'}) |
||||||
|
for key in sorted(MONTHS): # month columns |
||||||
|
fields.append({'name': MONTHS[key], 'type': 'INTEGER', |
||||||
|
'calculation': 'COALESCE(SUM(CASE ' + 'month' + ' WHEN ' + str( |
||||||
|
key) + ' THEN ' + 'metric' + ' END), 0)'}) |
||||||
|
# TODO add cost table fields |
||||||
|
return fields |
||||||
|
|
||||||
|
|
||||||
|
def create_table_sql_texts(reports): # makes the SQL statements to create the tables from the table definition |
||||||
|
sql_texts = {} |
||||||
|
for report in reports: |
||||||
|
sql_text = 'CREATE TABLE IF NOT EXISTS ' + report + '(' |
||||||
|
report_fields = get_report_fields_list(report, False) |
||||||
|
fields_and_options = [] |
||||||
|
key_fields = [] |
||||||
|
for field in report_fields: # fields specific to this report |
||||||
|
field_text = field['name'] + ' ' + field['type'] |
||||||
|
if field['options']: |
||||||
|
field_text += ' ' + ' '.join(field['options']) |
||||||
|
fields_and_options.append(field_text) |
||||||
|
if field['name'] not in FIELDS_NOT_IN_KEYS: |
||||||
|
key_fields.append(field['name']) |
||||||
|
sql_text += '\n\t' + ', \n\t'.join(fields_and_options) + ',\n\tPRIMARY KEY(' + ', '.join(key_fields) + '));' |
||||||
|
sql_texts[report] = sql_text |
||||||
|
return sql_texts |
||||||
|
|
||||||
|
|
||||||
|
def create_view_sql_texts(reports): # makes the SQL statements to create the views from the table definition |
||||||
|
sql_texts = {} |
||||||
|
for report in reports: |
||||||
|
sql_text = 'CREATE VIEW IF NOT EXISTS ' + report + VIEW_SUFFIX + ' AS SELECT' |
||||||
|
report_fields = get_report_fields_list(report, True) |
||||||
|
fields = [] |
||||||
|
calcs = [] |
||||||
|
for field in report_fields: # fields specific to this report |
||||||
|
if 'calculation' not in field.keys(): |
||||||
|
fields.append(field['name']) |
||||||
|
else: |
||||||
|
calcs.append(field['calculation'] + ' AS ' + field['name']) |
||||||
|
sql_text += '\n\t' + ', \n\t'.join(fields) + ', \n\t' + ', \n\t'.join(calcs) |
||||||
|
sql_text += '\nFROM ' + report |
||||||
|
sql_text += '\nGROUP BY ' + ', '.join(fields) + ';' |
||||||
|
sql_texts[report + VIEW_SUFFIX] = sql_text |
||||||
|
return sql_texts |
||||||
|
|
||||||
|
|
||||||
|
# TODO add create cost tables |
||||||
|
|
||||||
|
|
||||||
|
# TODO add create combined cost table views |
||||||
|
|
||||||
|
|
||||||
|
# TODO add create visualisation views |
||||||
|
|
||||||
|
|
||||||
|
def replace_sql_text(file_name, report, data): # makes the sql statement to 'replace or insert' data into a table |
||||||
|
sql_replace_text = 'REPLACE INTO ' + report + '(' |
||||||
|
report_fields = get_report_fields_list(report, False) |
||||||
|
fields = [] |
||||||
|
types = {} |
||||||
|
for field in report_fields: # fields specific to this report |
||||||
|
fields.append(field['name']) |
||||||
|
types[field['name']] = field['type'] |
||||||
|
sql_replace_text += ', '.join(fields) + ')' |
||||||
|
sql_replace_text += '\nVALUES' |
||||||
|
placeholders = [] |
||||||
|
for key in fields: # gets parameter slots |
||||||
|
placeholders.append('?') |
||||||
|
sql_replace_text += '(' + ', '.join(placeholders) + ');' |
||||||
|
values = [] |
||||||
|
for row in data: # gets data to fill parameters |
||||||
|
row_values = [] |
||||||
|
for key in fields: |
||||||
|
value = None |
||||||
|
if row.get(key): |
||||||
|
value = row.get(key) |
||||||
|
else: |
||||||
|
value = '' # if empty, use empty string |
||||||
|
row_values.append(value) |
||||||
|
values.append(row_values) |
||||||
|
sql_delete_text = 'DELETE FROM ' + report + ' WHERE ' + 'file' + ' = \"' + file_name + '\";' |
||||||
|
return {'sql_delete': sql_delete_text, 'sql_replace': sql_replace_text, 'data': values} |
||||||
|
|
||||||
|
|
||||||
|
def read_report_file(file_name, vendor, |
||||||
|
year): # reads a specific csv/tsv file and returns the report type and the values for inserting |
||||||
|
delimiter = DELIMITERS[file_name[-4:].lower()] |
||||||
|
file = open(file_name, 'r', encoding='utf-8') |
||||||
|
reader = csv.reader(file, delimiter=delimiter, quotechar='\"') |
||||||
|
results = {} |
||||||
|
if file.mode == 'r': |
||||||
|
header = {} |
||||||
|
for row in range(HEADER_ROWS): # reads header row data |
||||||
|
cells = next(reader) |
||||||
|
key = cells[0].lower() |
||||||
|
if len(cells) > 1: |
||||||
|
header[key] = cells[1].strip() |
||||||
|
else: |
||||||
|
header[key] = None |
||||||
|
print(header) |
||||||
|
results['file'] = os.path.basename(file.name) |
||||||
|
results['report'] = header['report_id'] |
||||||
|
for row in range(BLANK_ROWS): |
||||||
|
next(reader) |
||||||
|
column_headers = next(reader) |
||||||
|
column_headers = list(map((lambda column_header: column_header.lower()), column_headers)) |
||||||
|
print(column_headers) |
||||||
|
values = [] |
||||||
|
for cells in list(reader): |
||||||
|
for month in MONTHS: # makes value from each month with metric > 0 for each row |
||||||
|
month_header = MONTHS[month][:3].lower() + '-' + str(year) |
||||||
|
if month_header in column_headers: |
||||||
|
current_month = column_headers.index(month_header) |
||||||
|
metric = int(cells[current_month]) |
||||||
|
if metric > 0: |
||||||
|
value = {} |
||||||
|
last_column = column_headers.index(YEAR_TOTAL) |
||||||
|
for i in range(last_column): # read rows before months |
||||||
|
value[column_headers[i]] = cells[i] |
||||||
|
if not value['metric_type']: # if no metric type column, use the metric type from header |
||||||
|
value['metric_type'] = header['metric_types'] |
||||||
|
# additional fields |
||||||
|
value['year'] = year |
||||||
|
value['month'] = month |
||||||
|
value['metric'] = int(cells[current_month]) |
||||||
|
value['vendor'] = vendor |
||||||
|
value['updated_on'] = header['created'] |
||||||
|
value['file'] = os.path.basename(file.name) |
||||||
|
values.append(value) |
||||||
|
results['values'] = values |
||||||
|
return results |
||||||
|
else: |
||||||
|
print('Error: could not open file ' + file_name) |
||||||
|
return None |
||||||
|
|
||||||
|
|
||||||
|
def get_all_reports(): |
||||||
|
reports = [] |
||||||
|
for upper_directory in os.scandir(FILE_LOCATION): # iterate over all files in FILE_LOCATION |
||||||
|
for lower_directory in os.scandir(upper_directory): |
||||||
|
directory_data = {FILE_SUBDIRECTORY_ORDER[0]: upper_directory.name, |
||||||
|
FILE_SUBDIRECTORY_ORDER[1]: lower_directory.name} # get data from directory names |
||||||
|
for file in os.scandir(lower_directory): |
||||||
|
if file.name[-4:] in DELIMITERS: |
||||||
|
reports.append({'file': file.path, 'vendor': directory_data['vendor'], |
||||||
|
'year': directory_data['year']}) |
||||||
|
return reports |
||||||
|
|
||||||
|
|
||||||
|
def insert_single_file(file_path, vendor, year): |
||||||
|
data = read_report_file(file_path, vendor, year) |
||||||
|
replace = replace_sql_text(data['file'], data['report'], data['values']) |
||||||
|
|
||||||
|
connection = create_connection(DATABASE_LOCATION) |
||||||
|
if connection is not None: |
||||||
|
run_insert_sql(connection, replace['sql_delete'], replace['sql_replace'], replace['data']) |
||||||
|
connection.close() |
||||||
|
else: |
||||||
|
print('Error, no connection') |
||||||
|
|
||||||
|
|
||||||
|
def search_sql_text(report, start_year, end_year, |
||||||
|
search_parameters): # makes the sql statement to search the database; search_parameters in POS form |
||||||
|
sql_text = 'SELECT * FROM ' + report + VIEW_SUFFIX |
||||||
|
sql_text += '\nWHERE' |
||||||
|
clauses = [[{'field': 'year', 'comparison': '>=', 'value': start_year}], |
||||||
|
[{'field': 'year', 'comparison': '<=', 'value': end_year}]] |
||||||
|
clauses.extend(search_parameters) |
||||||
|
print(clauses) |
||||||
|
clauses_texts = [] |
||||||
|
for clause in clauses: |
||||||
|
sub_clauses_text = [] |
||||||
|
for sub_clause in clause: |
||||||
|
sub_clauses_text.append( |
||||||
|
sub_clause['field'] + ' ' + sub_clause['comparison'] + ' \'' + str(sub_clause['value']) + '\'') |
||||||
|
# TODO make parameterized query |
||||||
|
clauses_texts.append('(' + ' OR '.join(sub_clauses_text) + ')') |
||||||
|
sql_text += '\n\t' + '\n\tAND '.join(clauses_texts) |
||||||
|
sql_text += ';' |
||||||
|
return sql_text |
||||||
|
|
||||||
|
|
||||||
|
def create_connection(db_file): |
||||||
|
connection = None |
||||||
|
try: |
||||||
|
connection = sqlite3.connect(db_file) |
||||||
|
print(sqlite3.version) |
||||||
|
return connection |
||||||
|
except sqlite3.Error as error: |
||||||
|
print(error) |
||||||
|
connection.close() |
||||||
|
return connection |
||||||
|
|
||||||
|
|
||||||
|
def run_sql(connection, sql_text): |
||||||
|
try: |
||||||
|
cursor = connection.cursor() |
||||||
|
cursor.execute(sql_text) |
||||||
|
except sqlite3.Error as error: |
||||||
|
print(error) |
||||||
|
|
||||||
|
|
||||||
|
def run_insert_sql(connection, sql_delete_text, sql_insert_text, data): |
||||||
|
try: |
||||||
|
cursor = connection.cursor() |
||||||
|
print(sql_delete_text) |
||||||
|
cursor.execute(sql_delete_text) |
||||||
|
print(sql_insert_text) |
||||||
|
cursor.executemany(sql_insert_text, data) |
||||||
|
connection.commit() |
||||||
|
except sqlite3.Error as error: |
||||||
|
print(error) |
||||||
|
|
||||||
|
|
||||||
|
def run_select_sql(connection, sql_text): |
||||||
|
try: |
||||||
|
cursor = connection.cursor() |
||||||
|
cursor.execute(sql_text) |
||||||
|
return cursor.fetchall() |
||||||
|
except sqlite3.Error as error: |
||||||
|
print(error) |
||||||
|
return None |
||||||
|
|
||||||
|
|
||||||
|
def setup_database(drop_tables): |
||||||
|
if not os.path.exists(DATABASE_FOLDER): |
||||||
|
os.mkdir(DATABASE_FOLDER) |
||||||
|
sql_texts = {} |
||||||
|
sql_texts.update(create_table_sql_texts(DATABASE_REPORTS)) |
||||||
|
sql_texts.update(create_view_sql_texts(DATABASE_REPORTS)) |
||||||
|
sql_texts.update(create_table_sql_texts(ITEM_REPORTS)) |
||||||
|
sql_texts.update(create_view_sql_texts(ITEM_REPORTS)) |
||||||
|
sql_texts.update(create_table_sql_texts(PLATFORM_REPORTS)) |
||||||
|
sql_texts.update(create_view_sql_texts(PLATFORM_REPORTS)) |
||||||
|
sql_texts.update(create_table_sql_texts(TITLE_REPORTS)) |
||||||
|
sql_texts.update(create_view_sql_texts(TITLE_REPORTS)) |
||||||
|
for key in sorted(sql_texts): # testing |
||||||
|
print(sql_texts[key]) |
||||||
|
|
||||||
|
connection = create_connection(DATABASE_LOCATION) |
||||||
|
if connection is not None: |
||||||
|
for key in sorted(sql_texts): |
||||||
|
if drop_tables: |
||||||
|
print('DROP ' + key) |
||||||
|
run_sql(connection, |
||||||
|
'DROP ' + ('VIEW' if key.endswith(VIEW_SUFFIX) else 'TABLE') + ' IF EXISTS ' + key + ';') |
||||||
|
print('CREATE ' + key) |
||||||
|
run_sql(connection, sql_texts[key]) |
||||||
|
connection.close() |
||||||
|
else: |
||||||
|
print('Error, no connection') |
||||||
|
|
||||||
|
|
||||||
|
def test_search(): # testing |
||||||
|
search = search_sql_text('DR_D1', 2019, 2019, |
||||||
|
[[{'field': 'database', 'comparison': 'like', 'value': '19th Century%'}], |
||||||
|
[{'field': 'publisher', 'comparison': '=', 'value': 'JSTOR'}, |
||||||
|
{'field': 'platform', 'comparison': '=', 'value': 'EBSCOhost'}]]) |
||||||
|
print(search) # testing |
||||||
|
|
||||||
|
connection = create_connection(DATABASE_LOCATION) |
||||||
|
if connection is not None: |
||||||
|
print(run_select_sql(connection, search)) |
||||||
|
connection.close() |
||||||
|
else: |
||||||
|
print('Error, no connection') |
@ -0,0 +1,446 @@ |
|||||||
|
import csv |
||||||
|
import json |
||||||
|
import validators |
||||||
|
from PyQt5.QtWidgets import QDialog, QLabel, QDialogButtonBox, QFileDialog |
||||||
|
from PyQt5.QtGui import QStandardItemModel, QStandardItem |
||||||
|
from PyQt5.QtCore import Qt, QObject, QModelIndex, pyqtSignal |
||||||
|
from ui import MainWindow, AddVendorDialog, MessageDialog, RemoveVendorDialog |
||||||
|
import DataStorage |
||||||
|
import webbrowser |
||||||
|
from JsonUtils import JsonModel |
||||||
|
|
||||||
|
VENDORS_FILE_DIR = "./all_data/vendor_manager/" |
||||||
|
VENDORS_FILE_NAME = "vendors.dat" |
||||||
|
VENDORS_FILE_PATH = VENDORS_FILE_DIR + VENDORS_FILE_NAME |
||||||
|
|
||||||
|
EXPORT_VENDORS_FILE_NAME = "exported_vendor_data.tsv" |
||||||
|
help_site = "https://github.com/CS-4820-Library-Project/Libly/wiki" |
||||||
|
|
||||||
|
|
||||||
|
class Vendor(JsonModel): |
||||||
|
def __init__(self, name: str, customer_id: str, base_url: str, requestor_id: str, api_key: str, platform: str, |
||||||
|
is_local: bool, description: str, companies: str): |
||||||
|
self.name = name |
||||||
|
self.customer_id = customer_id |
||||||
|
self.base_url = base_url |
||||||
|
self.requestor_id = requestor_id |
||||||
|
self.api_key = api_key |
||||||
|
self.platform = platform |
||||||
|
self.is_local = is_local |
||||||
|
self.description = description |
||||||
|
self.companies = companies |
||||||
|
|
||||||
|
@classmethod |
||||||
|
def from_json(cls, json_dict: dict): |
||||||
|
name = json_dict["name"] if "name" in json_dict else "" |
||||||
|
customer_id = json_dict["customer_id"] if "customer_id" in json_dict else "" |
||||||
|
base_url = json_dict["base_url"] if "base_url" in json_dict else "" |
||||||
|
requestor_id = json_dict["requestor_id"] if "requestor_id" in json_dict else "" |
||||||
|
api_key = json_dict["api_key"] if "api_key" in json_dict else "" |
||||||
|
platform = json_dict["platform"] if "platform" in json_dict else "" |
||||||
|
is_local = json_dict["is_local"] if "is_local" in json_dict else False |
||||||
|
description = json_dict["description"] if "description" in json_dict else "" |
||||||
|
companies = json_dict["companies"] if "companies" in json_dict else "" |
||||||
|
|
||||||
|
return cls(name, customer_id, base_url, requestor_id, api_key, platform, is_local, description, companies) |
||||||
|
|
||||||
|
|
||||||
|
class ManageVendorsController(QObject): |
||||||
|
vendors_changed_signal = pyqtSignal(list) |
||||||
|
|
||||||
|
def __init__(self, main_window_ui: MainWindow.Ui_mainWindow): |
||||||
|
super().__init__() |
||||||
|
self.selected_index = -1 |
||||||
|
|
||||||
|
self.edit_vendor_details_frame = main_window_ui.edit_vendor_details_frame |
||||||
|
self.edit_vendor_options_frame = main_window_ui.edit_vendor_options_frame |
||||||
|
|
||||||
|
self.name_line_edit = main_window_ui.nameEdit |
||||||
|
self.customer_id_line_edit = main_window_ui.customerIdEdit |
||||||
|
self.base_url_line_edit = main_window_ui.baseUrlEdit |
||||||
|
self.requestor_id_line_edit = main_window_ui.requestorIdEdit |
||||||
|
self.api_key_line_edit = main_window_ui.apiKeyEdit |
||||||
|
self.platform_line_edit = main_window_ui.platformEdit |
||||||
|
self.local_only_check_box = main_window_ui.local_only_check_box |
||||||
|
self.description_text_edit = main_window_ui.descriptionEdit |
||||||
|
self.companies_text_edit = main_window_ui.companiesEdit |
||||||
|
|
||||||
|
self.name_validation_label = main_window_ui.name_validation_label |
||||||
|
self.name_validation_label.hide() |
||||||
|
self.url_validation_label = main_window_ui.url_validation_label |
||||||
|
self.url_validation_label.hide() |
||||||
|
|
||||||
|
self.help_button = main_window_ui.helpButton |
||||||
|
self.save_vendor_changes_button = main_window_ui.saveVendorChangesButton |
||||||
|
self.undo_vendor_changes_button = main_window_ui.undoVendorChangesButton |
||||||
|
self.remove_vendor_button = main_window_ui.removeVendorButton |
||||||
|
self.add_vendor_button = main_window_ui.addVendorButton |
||||||
|
self.export_vendors_button = main_window_ui.exportVendorsButton |
||||||
|
# TODO(Ziheng): add export_vendors_button |
||||||
|
self.import_vendors_button = main_window_ui.importVendorsButton |
||||||
|
# TODO(Ziheng): add import_vendors_button |
||||||
|
|
||||||
|
self.help_button.clicked.connect(self.help_method) |
||||||
|
self.save_vendor_changes_button.clicked.connect(self.modify_vendor) |
||||||
|
self.undo_vendor_changes_button.clicked.connect(self.populate_edit_vendor_view) |
||||||
|
self.remove_vendor_button.clicked.connect(self.open_remove_vendor_dialog) |
||||||
|
self.add_vendor_button.clicked.connect(self.open_add_vendor_dialog) |
||||||
|
self.export_vendors_button.clicked.connect(self.open_custom_folder_select_dialog) |
||||||
|
# TODO(Ziheng): add connection to dialog for export_vendors_button to export dir path |
||||||
|
self.import_vendors_button.clicked.connect(self.open_file_select_dialog) |
||||||
|
# TODO(Ziheng): add connection to dialog for import_vendors_button to import file |
||||||
|
|
||||||
|
self.vendor_list_view = main_window_ui.vendorsListView |
||||||
|
self.vendor_list_model = QStandardItemModel(self.vendor_list_view) |
||||||
|
self.vendor_list_view.setModel(self.vendor_list_model) |
||||||
|
self.vendor_list_view.clicked.connect(self.on_vendor_selected) |
||||||
|
|
||||||
|
self.vendors = [] |
||||||
|
self.vendor_names = set() # Hash set for faster operations |
||||||
|
vendors_json_string = DataStorage.read_json_file(VENDORS_FILE_PATH) |
||||||
|
vendor_dicts = json.loads(vendors_json_string) |
||||||
|
for json_dict in vendor_dicts: |
||||||
|
vendor = Vendor.from_json(json_dict) |
||||||
|
self.vendors.append(vendor) |
||||||
|
self.vendor_names.add(vendor.name.lower()) |
||||||
|
|
||||||
|
self.update_vendors_ui() |
||||||
|
|
||||||
|
def on_vendor_selected(self, model_index: QModelIndex): |
||||||
|
self.selected_index = model_index.row() |
||||||
|
self.populate_edit_vendor_view() |
||||||
|
|
||||||
|
def on_name_text_changed(self, new_name: str, original_name: str, validation_label: QLabel, validate: bool = True): |
||||||
|
if not validate: |
||||||
|
validation_label.hide() |
||||||
|
return |
||||||
|
|
||||||
|
is_valid, message = self.validate_new_name(new_name, original_name) |
||||||
|
if is_valid: |
||||||
|
validation_label.hide() |
||||||
|
else: |
||||||
|
validation_label.show() |
||||||
|
validation_label.setText(message) |
||||||
|
|
||||||
|
def on_url_text_changed(self, url: str, validation_label: QLabel, validate: bool = True): |
||||||
|
if not validate: |
||||||
|
validation_label.hide() |
||||||
|
return |
||||||
|
|
||||||
|
is_valid, message = self.validate_url(url) |
||||||
|
if is_valid: |
||||||
|
validation_label.hide() |
||||||
|
else: |
||||||
|
validation_label.show() |
||||||
|
validation_label.setText(message) |
||||||
|
|
||||||
|
def validate_new_name(self, new_name: str, original_name: str = "") -> (bool, str): |
||||||
|
if not new_name: |
||||||
|
return False, "Vendor name can't be empty" |
||||||
|
elif new_name.lower() in self.vendor_names: |
||||||
|
if original_name and original_name.lower() == new_name.lower(): |
||||||
|
return True, "" |
||||||
|
else: |
||||||
|
return False, "Duplicate vendor name" |
||||||
|
else: |
||||||
|
return True, "" |
||||||
|
|
||||||
|
def validate_url(self, url: str) -> (bool, str): |
||||||
|
if not validators.url(url): |
||||||
|
return False, "Invalid Url" |
||||||
|
elif not url.endswith("/reports"): |
||||||
|
return False, "URL must end with '/reports'" |
||||||
|
else: |
||||||
|
return True, "" |
||||||
|
|
||||||
|
def update_vendors_ui(self): |
||||||
|
self.vendor_list_model.clear() |
||||||
|
for vendor in self.vendors: |
||||||
|
item = QStandardItem(vendor.name) |
||||||
|
item.setEditable(False) |
||||||
|
self.vendor_list_model.appendRow(item) |
||||||
|
|
||||||
|
self.populate_edit_vendor_view() |
||||||
|
|
||||||
|
def update_vendor_names(self): |
||||||
|
self.vendor_names.clear() |
||||||
|
for vendor in self.vendors: |
||||||
|
self.vendor_names.add(vendor.name.lower()) |
||||||
|
|
||||||
|
def add_vendor(self, new_vendor: Vendor) -> (bool, str): |
||||||
|
# Check if vendor is valid |
||||||
|
is_valid, message = self.validate_new_name(new_vendor.name) |
||||||
|
if not is_valid: |
||||||
|
return is_valid, message |
||||||
|
|
||||||
|
if not new_vendor.is_local: |
||||||
|
is_valid, message = self.validate_url(new_vendor.base_url) |
||||||
|
if not is_valid: |
||||||
|
return is_valid, message |
||||||
|
|
||||||
|
self.vendors.append(new_vendor) |
||||||
|
self.vendor_names.add(new_vendor.name.lower()) |
||||||
|
|
||||||
|
return True, "" |
||||||
|
|
||||||
|
def modify_vendor(self): |
||||||
|
if self.selected_index < 0: |
||||||
|
print("No vendor selected") |
||||||
|
return |
||||||
|
|
||||||
|
selected_vendor = self.vendors[self.selected_index] |
||||||
|
|
||||||
|
# Check if entries are valid |
||||||
|
new_name = self.name_line_edit.text() |
||||||
|
original_name = selected_vendor.name |
||||||
|
is_valid, message = self.validate_new_name(new_name, original_name) |
||||||
|
if not is_valid: |
||||||
|
self.show_message(message) |
||||||
|
return |
||||||
|
|
||||||
|
if not self.local_only_check_box.isChecked(): |
||||||
|
url = self.base_url_line_edit.text() |
||||||
|
is_valid, message = self.validate_url(url) |
||||||
|
if not is_valid: |
||||||
|
self.show_message(message) |
||||||
|
return |
||||||
|
|
||||||
|
# Apply Changes |
||||||
|
selected_vendor.name = self.name_line_edit.text() |
||||||
|
selected_vendor.base_url = self.base_url_line_edit.text() |
||||||
|
selected_vendor.customer_id = self.customer_id_line_edit.text() |
||||||
|
selected_vendor.requestor_id = self.requestor_id_line_edit.text() |
||||||
|
selected_vendor.api_key = self.api_key_line_edit.text() |
||||||
|
selected_vendor.platform = self.platform_line_edit.text() |
||||||
|
selected_vendor.is_local = self.local_only_check_box.checkState() == Qt.Checked |
||||||
|
selected_vendor.description = self.description_text_edit.toPlainText() |
||||||
|
selected_vendor.companies = self.companies_text_edit.toPlainText() |
||||||
|
|
||||||
|
self.update_vendors_ui() |
||||||
|
self.update_vendor_names() |
||||||
|
self.vendors_changed_signal.emit(self.vendors) |
||||||
|
self.save_all_vendors_to_disk() |
||||||
|
self.show_message("Changes Saved!") |
||||||
|
|
||||||
|
def open_add_vendor_dialog(self): |
||||||
|
vendor_dialog = QDialog() |
||||||
|
vendor_dialog_ui = AddVendorDialog.Ui_addVendorDialog() |
||||||
|
vendor_dialog_ui.setupUi(vendor_dialog) |
||||||
|
|
||||||
|
name_edit = vendor_dialog_ui.nameEdit |
||||||
|
base_url_edit = vendor_dialog_ui.baseUrlEdit |
||||||
|
customer_id_edit = vendor_dialog_ui.customerIdEdit |
||||||
|
requestor_id_edit = vendor_dialog_ui.requestorIdEdit |
||||||
|
api_key_edit = vendor_dialog_ui.apiKeyEdit |
||||||
|
platform_edit = vendor_dialog_ui.platformEdit |
||||||
|
local_only_check_box = vendor_dialog_ui.local_only_check_box |
||||||
|
description_edit = vendor_dialog_ui.descriptionEdit |
||||||
|
companies_edit = vendor_dialog_ui.companiesEdit |
||||||
|
|
||||||
|
name_validation_label = vendor_dialog_ui.name_validation_label |
||||||
|
name_validation_label.hide() |
||||||
|
url_validation_label = vendor_dialog_ui.url_validation_label |
||||||
|
url_validation_label.hide() |
||||||
|
|
||||||
|
name_edit.textChanged.connect( |
||||||
|
lambda new_name: self.on_name_text_changed(new_name, "", name_validation_label)) |
||||||
|
base_url_edit.textChanged.connect( |
||||||
|
lambda url: self.on_url_text_changed(url, url_validation_label)) |
||||||
|
|
||||||
|
def attempt_add_vendor(): |
||||||
|
vendor = Vendor(name_edit.text(), |
||||||
|
customer_id_edit.text(), |
||||||
|
base_url_edit.text(), |
||||||
|
requestor_id_edit.text(), |
||||||
|
api_key_edit.text(), |
||||||
|
platform_edit.text(), |
||||||
|
local_only_check_box.checkState() == Qt.Checked, |
||||||
|
description_edit.toPlainText(), |
||||||
|
companies_edit.toPlainText()) |
||||||
|
|
||||||
|
is_valid, message = self.add_vendor(vendor) |
||||||
|
if is_valid: |
||||||
|
self.sort_vendors() |
||||||
|
self.selected_index = -1 |
||||||
|
self.update_vendors_ui() |
||||||
|
self.populate_edit_vendor_view() |
||||||
|
self.vendors_changed_signal.emit(self.vendors) |
||||||
|
self.save_all_vendors_to_disk() |
||||||
|
vendor_dialog.close() |
||||||
|
else: |
||||||
|
self.show_message(message) |
||||||
|
|
||||||
|
button_box = vendor_dialog_ui.buttonBox |
||||||
|
ok_button = button_box.button(QDialogButtonBox.Ok) |
||||||
|
ok_button.clicked.connect(attempt_add_vendor) |
||||||
|
cancel_button = button_box.button(QDialogButtonBox.Cancel) |
||||||
|
cancel_button.clicked.connect(lambda: vendor_dialog.close()) |
||||||
|
|
||||||
|
vendor_dialog.exec_() |
||||||
|
|
||||||
|
def open_file_select_dialog(self): |
||||||
|
dialog = QFileDialog() |
||||||
|
dialog.setFileMode(QFileDialog.ExistingFile) |
||||||
|
if dialog.exec_(): |
||||||
|
selected_file_path = dialog.selectedFiles()[0] |
||||||
|
self.import_vendors_tsv(selected_file_path) |
||||||
|
self.show_message(f"Import successful!") |
||||||
|
|
||||||
|
def open_custom_folder_select_dialog(self): |
||||||
|
dialog = QFileDialog() |
||||||
|
dialog.setFileMode(QFileDialog.Directory) |
||||||
|
if dialog.exec_(): |
||||||
|
directory = dialog.selectedFiles()[0] + "/" |
||||||
|
self.export_vendors_tsv(directory) |
||||||
|
self.show_message(f"Exported as {EXPORT_VENDORS_FILE_NAME}") |
||||||
|
|
||||||
|
def populate_edit_vendor_view(self): |
||||||
|
if self.selected_index >= 0: |
||||||
|
selected_vendor = self.vendors[self.selected_index] |
||||||
|
|
||||||
|
self.name_line_edit.textChanged.connect( |
||||||
|
lambda new_name: self.on_name_text_changed(new_name, selected_vendor.name, self.name_validation_label)) |
||||||
|
self.name_line_edit.setText(selected_vendor.name) |
||||||
|
|
||||||
|
self.base_url_line_edit.textChanged.connect( |
||||||
|
lambda url: self.on_url_text_changed(url, self.url_validation_label)) |
||||||
|
self.base_url_line_edit.setText(selected_vendor.base_url) |
||||||
|
|
||||||
|
self.customer_id_line_edit.setText(selected_vendor.customer_id) |
||||||
|
self.requestor_id_line_edit.setText(selected_vendor.requestor_id) |
||||||
|
self.api_key_line_edit.setText(selected_vendor.api_key) |
||||||
|
self.platform_line_edit.setText(selected_vendor.platform) |
||||||
|
self.local_only_check_box.setChecked(selected_vendor.is_local) |
||||||
|
self.description_text_edit.setPlainText(selected_vendor.description) |
||||||
|
self.companies_text_edit.setPlainText(selected_vendor.companies) |
||||||
|
|
||||||
|
self.set_edit_vendor_view_state(True) |
||||||
|
else: |
||||||
|
self.name_line_edit.textChanged.connect( |
||||||
|
lambda new_name: self.on_name_text_changed(new_name, "", self.name_validation_label, False)) |
||||||
|
self.name_line_edit.setText("") |
||||||
|
self.name_line_edit.textChanged.emit("") # Hide validation_label if showing |
||||||
|
|
||||||
|
self.base_url_line_edit.textChanged.connect( |
||||||
|
lambda url: self.on_url_text_changed(url, self.url_validation_label, False)) |
||||||
|
self.base_url_line_edit.setText("") |
||||||
|
self.base_url_line_edit.textChanged.emit("") |
||||||
|
|
||||||
|
self.customer_id_line_edit.setText("") |
||||||
|
self.base_url_line_edit.setText("") |
||||||
|
self.requestor_id_line_edit.setText("") |
||||||
|
self.api_key_line_edit.setText("") |
||||||
|
self.platform_line_edit.setText("") |
||||||
|
self.local_only_check_box.setChecked(False) |
||||||
|
self.description_text_edit.setPlainText("") |
||||||
|
self.companies_text_edit.setPlainText("") |
||||||
|
|
||||||
|
self.set_edit_vendor_view_state(False) |
||||||
|
|
||||||
|
def set_edit_vendor_view_state(self, is_enabled): |
||||||
|
if is_enabled: |
||||||
|
self.edit_vendor_details_frame.setEnabled(True) |
||||||
|
self.edit_vendor_options_frame.setEnabled(True) |
||||||
|
else: |
||||||
|
self.edit_vendor_details_frame.setEnabled(False) |
||||||
|
self.edit_vendor_options_frame.setEnabled(False) |
||||||
|
|
||||||
|
def open_remove_vendor_dialog(self): |
||||||
|
dialog_remove = QDialog() |
||||||
|
dialog_remove_ui = RemoveVendorDialog.Ui_dialog_remove() |
||||||
|
dialog_remove_ui.setupUi(dialog_remove) |
||||||
|
|
||||||
|
def remove_vendor(): |
||||||
|
if self.selected_index >= 0: |
||||||
|
self.vendors.pop(self.selected_index) |
||||||
|
self.selected_index = -1 |
||||||
|
|
||||||
|
self.update_vendors_ui() |
||||||
|
self.update_vendor_names() |
||||||
|
self.populate_edit_vendor_view() |
||||||
|
self.vendors_changed_signal.emit(self.vendors) |
||||||
|
self.save_all_vendors_to_disk() |
||||||
|
|
||||||
|
button_box = dialog_remove_ui.buttonBox |
||||||
|
button_box.accepted.connect(remove_vendor) |
||||||
|
dialog_remove.exec_() |
||||||
|
|
||||||
|
def save_all_vendors_to_disk(self): |
||||||
|
json_string = json.dumps(self.vendors, default=lambda o: o.__dict__, indent=4) |
||||||
|
DataStorage.save_json_file(VENDORS_FILE_DIR, VENDORS_FILE_NAME, json_string) |
||||||
|
|
||||||
|
def show_message(self, message: str): |
||||||
|
message_dialog = QDialog(flags=Qt.WindowCloseButtonHint) |
||||||
|
message_dialog_ui = MessageDialog.Ui_message_dialog() |
||||||
|
message_dialog_ui.setupUi(message_dialog) |
||||||
|
|
||||||
|
message_label = message_dialog_ui.message_label |
||||||
|
message_label.setText(message) |
||||||
|
|
||||||
|
message_dialog.exec_() |
||||||
|
|
||||||
|
def sort_vendors(self): |
||||||
|
self.vendors = sorted(self.vendors, key=lambda vendor: vendor.name.lower()) |
||||||
|
|
||||||
|
def import_vendors_tsv(self, file_path): |
||||||
|
try: |
||||||
|
tsv_file = open(file_path, 'r', encoding="utf-8", newline='') |
||||||
|
reader = csv.DictReader(tsv_file, delimiter='\t') |
||||||
|
for row in reader: |
||||||
|
if 'is_local' in row: |
||||||
|
is_local = row['is_local'].lower() == "true" |
||||||
|
else: |
||||||
|
is_local = False |
||||||
|
vendor = Vendor(row['name'] if 'name' in row else "", |
||||||
|
row['customer_id'] if 'customer_id' in row else "", |
||||||
|
row['base_url'] if 'base_url' in row else "", |
||||||
|
row['requestor_id'] if 'requestor_id' in row else "", |
||||||
|
row['api_key'] if 'api_key' in row else "", |
||||||
|
row['platform'] if 'platform' in row else "", |
||||||
|
is_local, |
||||||
|
row['description'] if 'description' in row else "", |
||||||
|
row['companies'] if 'companies' in row else "") |
||||||
|
|
||||||
|
is_valid, message = self.add_vendor(vendor) |
||||||
|
if not is_valid: |
||||||
|
print(message) |
||||||
|
|
||||||
|
tsv_file.close() |
||||||
|
|
||||||
|
self.sort_vendors() |
||||||
|
self.selected_index = -1 |
||||||
|
self.update_vendors_ui() |
||||||
|
self.update_vendor_names() |
||||||
|
self.populate_edit_vendor_view() |
||||||
|
self.vendors_changed_signal.emit(self.vendors) |
||||||
|
self.save_all_vendors_to_disk() |
||||||
|
except Exception as e: |
||||||
|
print(f"File import failed: {e}") |
||||||
|
|
||||||
|
def export_vendors_tsv(self, dir_path): |
||||||
|
file_path = f"{dir_path}{EXPORT_VENDORS_FILE_NAME}" |
||||||
|
column_names = ["name", |
||||||
|
"customer_id", |
||||||
|
"base_url", |
||||||
|
"requestor_id", |
||||||
|
"api_key", |
||||||
|
"platform", |
||||||
|
"is_local", |
||||||
|
"description", |
||||||
|
"companies"] |
||||||
|
try: |
||||||
|
tsv_file = open(file_path, 'w', encoding="utf-8", newline='') |
||||||
|
tsv_dict_writer = csv.DictWriter(tsv_file, column_names, delimiter='\t') |
||||||
|
tsv_dict_writer.writeheader() |
||||||
|
|
||||||
|
for vendor in self.vendors: |
||||||
|
tsv_dict_writer.writerow(vendor.__dict__) |
||||||
|
|
||||||
|
tsv_file.close() |
||||||
|
|
||||||
|
except Exception as e: |
||||||
|
print(f"File export failed: {e}") |
||||||
|
|
||||||
|
def help_method(self): |
||||||
|
webbrowser.open(help_site, 2, True) |
||||||
|
|
@ -0,0 +1,59 @@ |
|||||||
|
# Libly |
||||||
|
|
||||||
|
This project is written is Python 3.7. Anaconda is used to manage the packages and environment used in this project, everything is up to date. |
||||||
|
The required packages are PyQt5 and requests. |
||||||
|
|
||||||
|
The project's UI is developed using PyQt |
||||||
|
|
||||||
|
# Setup Instructions (Windows) |
||||||
|
- Install Python 3.8.2 [https://www.python.org/ftp/python/3.8.2/python-3.8.2-amd64.exe] |
||||||
|
### Add Python to environment variables [https://datatofish.com/add-python-to-windows-path/] |
||||||
|
- Open control panel |
||||||
|
- System and Security -> System -> Advanced System Settings -> Advanced -> Environment Variables |
||||||
|
- System Variables: Click on Path, Click Edit... |
||||||
|
- Click Browse |
||||||
|
- Browse to where python is downloaded, by default: C:\Users\USER_NAME\AppData\Local\Programs\Python\Python38 |
||||||
|
|
||||||
|
- Add another variable in the same way that we just did. |
||||||
|
- Except this time set the filepath to C:\Users\apjm4\AppData\Local\Programs\Python\Python38\Scripts |
||||||
|
Python should now be accessible in Windows Command Prompt. |
||||||
|
Open command prompt and type Python --version. This should return the version of python that is installed. |
||||||
|
If not the path may be wrong or python was not installed correctly. |
||||||
|
|
||||||
|
type pip -v into command prompt, this should return the version of PIP that is installed with Python. |
||||||
|
|
||||||
|
### Download the project from Github |
||||||
|
- Close and re-open command prompt |
||||||
|
- type cd |
||||||
|
- Open the location you downloaded the project to and drag the folder into the command prompt window |
||||||
|
- Your command prompt window should now show "C:\Users\NAME>cd C:\Users\NAME\DOWNLOAD_LOCATION |
||||||
|
- Hit ENTER |
||||||
|
- type: pip install -r requirements.txt |
||||||
|
- This installs all the neccessary packages to run the project. |
||||||
|
|
||||||
|
### Run the project |
||||||
|
- Type: python maindriver.py |
||||||
|
- A User-Interface window should open with the project working |
||||||
|
- To run the project from now on, you only need to double click or right click and open MainDriver.py and the project should open |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Developer Setup (using Anaconda and Pycharm) |
||||||
|
- Download and install Anaconda: https://www.anaconda.com/distribution/#download-section |
||||||
|
- Download and install PyCharm: https://www.jetbrains.com/pycharm/download/ |
||||||
|
|
||||||
|
## Using Anaconda |
||||||
|
- Launch Anaconda Navigator (Anaconda GUI) |
||||||
|
- Go to Environments on the left pane |
||||||
|
- Search for and ensure that pyqt and requests packages are installed |
||||||
|
|
||||||
|
## Using PyCharm |
||||||
|
- Download and open the project using PyCharm |
||||||
|
- Go to File->Settings |
||||||
|
- On the left pane, select Project->Project Interpreter |
||||||
|
- Click the cog wheel on the right of the project interpreter drop down, click add |
||||||
|
- Choose Existing environment and set the location to anaconda_install_location/python.exe, OK, OK |
||||||
|
- Allow the IDE to complete set up then launch the program from MainDriver.py. There should be a play icon next to the line "if __name__ == "__main__":" |
||||||
|
- We Good To Go! |
||||||
|
|
||||||
|
|
@ -0,0 +1,242 @@ |
|||||||
|
import csv |
||||||
|
import os |
||||||
|
import shlex |
||||||
|
import sip |
||||||
|
import json |
||||||
|
from PyQt5.QtCore import QDate |
||||||
|
from PyQt5.QtWidgets import QFrame, QVBoxLayout, QComboBox, QLineEdit, QFileDialog |
||||||
|
|
||||||
|
import ManageDB |
||||||
|
import DataStorage |
||||||
|
from ui import MainWindow, SearchAndClauseFrame, SearchOrClauseFrame |
||||||
|
|
||||||
|
|
||||||
|
class SearchController: |
||||||
|
def __init__(self, main_window_ui: MainWindow.Ui_mainWindow): |
||||||
|
self.main_window = main_window_ui |
||||||
|
|
||||||
|
# set up report types combobox |
||||||
|
self.report_parameter = main_window_ui.search_report_parameter_combobox |
||||||
|
self.report_parameter.addItems(ManageDB.DATABASE_REPORTS) |
||||||
|
self.report_parameter.addItems(ManageDB.ITEM_REPORTS) |
||||||
|
self.report_parameter.addItems(ManageDB.PLATFORM_REPORTS) |
||||||
|
self.report_parameter.addItems(ManageDB.TITLE_REPORTS) |
||||||
|
|
||||||
|
# set up start year dateedit |
||||||
|
self.start_year_parameter = main_window_ui.search_start_year_parameter_dateedit |
||||||
|
self.start_year_parameter.setDate(QDate.currentDate()) |
||||||
|
|
||||||
|
# set up end year dateedit |
||||||
|
self.end_year_parameter = main_window_ui.search_end_year_parameter_dateedit |
||||||
|
self.end_year_parameter.setDate(QDate.currentDate()) |
||||||
|
|
||||||
|
# set up search button |
||||||
|
self.search_button = main_window_ui.search_button |
||||||
|
self.search_button.clicked.connect(self.search) |
||||||
|
|
||||||
|
self.open_results_checkbox = main_window_ui.search_open_results_checkbox |
||||||
|
|
||||||
|
# set up export button |
||||||
|
self.export_button = main_window_ui.search_export_button |
||||||
|
self.export_button.clicked.connect(self.export_parameters) |
||||||
|
|
||||||
|
# set up import button |
||||||
|
self.import_button = main_window_ui.search_import_button |
||||||
|
self.import_button.clicked.connect(self.import_parameters) |
||||||
|
|
||||||
|
# set up restore database button |
||||||
|
self.restore_database_button = main_window_ui.search_restore_database_button |
||||||
|
self.restore_database_button.clicked.connect(self.restore_database) |
||||||
|
|
||||||
|
# set up add and clause button |
||||||
|
def add_and_and_or_clause(): |
||||||
|
and_clause = self.add_and_clause() |
||||||
|
self.add_or_clause(and_clause) |
||||||
|
|
||||||
|
self.add_and_button = main_window_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(): |
||||||
|
self.refresh_clauses() |
||||||
|
add_and_and_or_clause() |
||||||
|
self.report_parameter.currentTextChanged.connect(refresh_and_add_clauses) |
||||||
|
|
||||||
|
self.and_clause_parameters = None |
||||||
|
refresh_and_add_clauses() |
||||||
|
|
||||||
|
def refresh_clauses(self): # resets the search clauses |
||||||
|
self.and_clause_parameters = QFrame() |
||||||
|
self.and_clause_parameters.setLayout(QVBoxLayout()) |
||||||
|
self.main_window.search_and_clause_parameters_scrollarea.setWidget(self.and_clause_parameters) |
||||||
|
|
||||||
|
def add_and_clause(self): |
||||||
|
and_clause = QFrame() |
||||||
|
and_clause_ui = SearchAndClauseFrame.Ui_search_and_clause_parameter_frame() |
||||||
|
and_clause_ui.setupUi(and_clause) |
||||||
|
|
||||||
|
# set up add or clause button |
||||||
|
def add_or_to_this_and(): |
||||||
|
self.add_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(): |
||||||
|
self.and_clause_parameters.layout().removeWidget(and_clause) |
||||||
|
sip.delete(and_clause) |
||||||
|
|
||||||
|
and_clause_ui.search_remove_and_clause_button.clicked.connect(remove_this_and) |
||||||
|
|
||||||
|
# add to the layout |
||||||
|
self.and_clause_parameters.layout().addWidget(and_clause) |
||||||
|
|
||||||
|
return and_clause_ui |
||||||
|
|
||||||
|
def add_or_clause(self, and_clause): |
||||||
|
or_clause = QFrame() |
||||||
|
or_clause_ui = SearchOrClauseFrame.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_report_fields_list(self.report_parameter.currentText(), True): |
||||||
|
if 'calculation' not in field.keys() and field['name'] not in ManageDB.FIELDS_NOT_IN_SEARCH: |
||||||
|
field_combobox.addItem(field['name']) |
||||||
|
|
||||||
|
# TODO make value check for type |
||||||
|
|
||||||
|
# fill comparison operator combobox |
||||||
|
comparison_combobox = or_clause_ui.search_comparison_parameter_combobox |
||||||
|
comparison_combobox.addItems(('=', '<=', '<', '>=', '>', 'LIKE')) |
||||||
|
|
||||||
|
# set up remove current or clause button |
||||||
|
def remove_this_or(): |
||||||
|
and_clause.search_or_clause_parameters_frame.layout().removeWidget(or_clause) |
||||||
|
sip.delete(or_clause) |
||||||
|
|
||||||
|
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 export_parameters(self): # export current search parameters to selected file |
||||||
|
parameters = self.get_search_parameters() |
||||||
|
print(parameters) |
||||||
|
dialog = QFileDialog() |
||||||
|
dialog.setFileMode(QFileDialog.AnyFile) |
||||||
|
dialog.setAcceptMode(QFileDialog.AcceptSave) |
||||||
|
dialog.setNameFilter('JSON files (*.dat)') |
||||||
|
if dialog.exec_(): |
||||||
|
file_name = dialog.selectedFiles()[0] |
||||||
|
if file_name[-4:].lower() != '.dat' and file_name != '': |
||||||
|
file_name += '.tsv' |
||||||
|
file = open(file_name, 'w', encoding='utf-8') |
||||||
|
if file.mode == 'w': |
||||||
|
json.dump(parameters, file) |
||||||
|
|
||||||
|
def import_parameters(self): # import search parameters from selected file |
||||||
|
dialog = QFileDialog() |
||||||
|
dialog.setFileMode(QFileDialog.ExistingFile) |
||||||
|
dialog.setNameFilter('JSON files (*.dat)') |
||||||
|
if dialog.exec_(): |
||||||
|
fields = json.loads(DataStorage.read_json_file(dialog.selectedFiles()[0])) |
||||||
|
print(fields) |
||||||
|
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'] |
||||||
|
print(clauses) |
||||||
|
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']) |
||||||
|
or_clause.search_comparison_parameter_combobox.setCurrentText(sub_clause['comparison']) |
||||||
|
or_clause.search_value_parameter_lineedit.setText(sub_clause['value']) |
||||||
|
|
||||||
|
def search(self): # submit search result to database and open results |
||||||
|
parameters = self.get_search_parameters() |
||||||
|
|
||||||
|
# sql query to get search results |
||||||
|
sql_text = ManageDB.search_sql_text(parameters['report'], parameters['start_year'], |
||||||
|
parameters['end_year'], parameters['search_parameters']) |
||||||
|
print(sql_text) # testing |
||||||
|
|
||||||
|
headers = [] |
||||||
|
for field in ManageDB.get_report_fields_list(parameters['report'], True): |
||||||
|
headers.append(field['name']) |
||||||
|
|
||||||
|
dialog = QFileDialog() |
||||||
|
dialog.setFileMode(QFileDialog.AnyFile) |
||||||
|
dialog.setAcceptMode(QFileDialog.AcceptSave) |
||||||
|
dialog.setNameFilter('TSV files (*.tsv)') |
||||||
|
if dialog.exec_(): |
||||||
|
file_name = dialog.selectedFiles()[0] |
||||||
|
if file_name[-4:].lower() != '.tsv' and file_name != '': |
||||||
|
file_name += '.tsv' |
||||||
|
connection = ManageDB.create_connection(ManageDB.DATABASE_LOCATION) |
||||||
|
if connection is not None: |
||||||
|
results = ManageDB.run_select_sql(connection, sql_text) |
||||||
|
results.insert(0, headers) |
||||||
|
print(results) |
||||||
|
file = open(file_name, 'w', newline="", encoding='utf-8') |
||||||
|
if file.mode == 'w': |
||||||
|
output = csv.writer(file, delimiter='\t', quotechar='\"') |
||||||
|
for row in results: |
||||||
|
output.writerow(row) |
||||||
|
|
||||||
|
open_file_switcher = {'nt': (lambda: os.startfile(file_name)), |
||||||
|
# TODO check file_name for special characters and quote |
||||||
|
'posix': (lambda: os.system("open " + shlex.quote(file_name)))} |
||||||
|
if self.open_results_checkbox.isChecked(): |
||||||
|
open_file_switcher[os.name]() |
||||||
|
else: |
||||||
|
print('Error: could not open file ' + file_name) |
||||||
|
|
||||||
|
connection.close() |
||||||
|
else: |
||||||
|
print('Error, no connection') |
||||||
|
else: |
||||||
|
print('Error, no file location selected') |
||||||
|
|
||||||
|
def get_search_parameters(self): |
||||||
|
# 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.findChildren(QFrame, 'search_and_clause_parameter_frame'): |
||||||
|
# iterate over and clauses |
||||||
|
print('and: ' + str(and_widget.objectName()) + ' ' + str(and_widget)) # testing |
||||||
|
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 |
||||||
|
print('\tor: ' + str(or_widget.objectName()) + ' ' + str(or_widget)) # testing |
||||||
|
# get parameters for clause |
||||||
|
field_parameter = or_widget.findChild(QComboBox, 'search_field_parameter_combobox').currentText() |
||||||
|
comparison_parameter = or_widget.findChild(QComboBox, |
||||||
|
'search_comparison_parameter_combobox').currentText() |
||||||
|
value_parameter = or_widget.findChild(QLineEdit, 'search_value_parameter_lineedit').text() |
||||||
|
# TODO check for special characters |
||||||
|
or_clauses.append( |
||||||
|
{'field': field_parameter, 'comparison': comparison_parameter, 'value': value_parameter}) |
||||||
|
search_parameters.append(or_clauses) |
||||||
|
|
||||||
|
return {'report': report, 'start_year': start_year, 'end_year': end_year, |
||||||
|
'search_parameters': search_parameters} |
||||||
|
|
||||||
|
def restore_database(self): |
||||||
|
ManageDB.setup_database(True) |
||||||
|
reports = ManageDB.get_all_reports() |
||||||
|
for report in reports: |
||||||
|
print(os.path.basename(report['file'])) |
||||||
|
ManageDB.insert_single_file(report['file'], report['vendor'], report['year']) |
||||||
|
print('done') |
@ -0,0 +1,166 @@ |
|||||||
|
from enum import Enum |
||||||
|
from PyQt5.QtCore import Qt, QObject, pyqtSignal |
||||||
|
from PyQt5.QtWidgets import QDialog, QFileDialog |
||||||
|
from ui import MainWindow, MessageDialog |
||||||
|
from JsonUtils import JsonModel |
||||||
|
import json |
||||||
|
import DataStorage |
||||||
|
|
||||||
|
SETTINGS_FILE_DIR = "./all_data/settings/" |
||||||
|
SETTINGS_FILE_NAME = "settings.dat" |
||||||
|
|
||||||
|
|
||||||
|
class Setting(Enum): |
||||||
|
YEARLY_DIR = 0 |
||||||
|
OTHER_DIR = 1 |
||||||
|
REQUEST_INTERVAL = 2 |
||||||
|
REQUEST_TIMEOUT = 3 |
||||||
|
CONCURRENT_VENDORS = 4 |
||||||
|
CONCURRENT_REPORTS = 5 |
||||||
|
EMPTY_CELL = 6 |
||||||
|
|
||||||
|
|
||||||
|
# Default Settings |
||||||
|
YEARLY_DIR = "./all_data/yearly_files/" |
||||||
|
OTHER_DIR = "./all_data/other_files/" |
||||||
|
REQUEST_INTERVAL = 3 # Seconds |
||||||
|
REQUEST_TIMEOUT = 120 # Seconds |
||||||
|
CONCURRENT_VENDORS = 5 |
||||||
|
CONCURRENT_REPORTS = 5 |
||||||
|
EMPTY_CELL = "" |
||||||
|
|
||||||
|
|
||||||
|
class SettingsModel(JsonModel): |
||||||
|
def __init__(self, yearly_directory: str, other_directory: str, request_interval: int, request_timeout: int, |
||||||
|
concurrent_vendors: int, concurrent_reports: int, empty_cell: str): |
||||||
|
self.yearly_directory = yearly_directory |
||||||
|
self.other_directory = other_directory |
||||||
|
self.request_interval = request_interval |
||||||
|
self.request_timeout = request_timeout |
||||||
|
self.concurrent_vendors = concurrent_vendors |
||||||
|
self.concurrent_reports = concurrent_reports |
||||||
|
self.empty_cell = empty_cell |
||||||
|
|
||||||
|
@classmethod |
||||||
|
def from_json(cls, json_dict: dict): |
||||||
|
yearly_directory = json_dict["yearly_directory"]\ |
||||||
|
if "yearly_directory" in json_dict else YEARLY_DIR |
||||||
|
other_directory = json_dict["other_directory"]\ |
||||||
|
if "other_directory" in json_dict else OTHER_DIR |
||||||
|
request_interval = int(json_dict["request_interval"])\ |
||||||
|
if "request_interval" in json_dict else REQUEST_INTERVAL |
||||||
|
request_timeout = int(json_dict["request_timeout"])\ |
||||||
|
if "request_timeout" in json_dict else REQUEST_TIMEOUT |
||||||
|
concurrent_vendors = int(json_dict["concurrent_vendors"])\ |
||||||
|
if "concurrent_vendors" in json_dict else CONCURRENT_VENDORS |
||||||
|
concurrent_reports = int(json_dict["concurrent_reports"])\ |
||||||
|
if "concurrent_reports" in json_dict else CONCURRENT_REPORTS |
||||||
|
empty_cell = json_dict["empty_cell"]\ |
||||||
|
if "empty_cell" in json_dict else EMPTY_CELL |
||||||
|
|
||||||
|
return cls(yearly_directory, other_directory, request_interval, request_timeout, concurrent_vendors, |
||||||
|
concurrent_reports, empty_cell) |
||||||
|
|
||||||
|
|
||||||
|
def show_message(message: str): |
||||||
|
message_dialog = QDialog(flags=Qt.WindowCloseButtonHint) |
||||||
|
message_dialog_ui = MessageDialog.Ui_message_dialog() |
||||||
|
message_dialog_ui.setupUi(message_dialog) |
||||||
|
|
||||||
|
message_label = message_dialog_ui.message_label |
||||||
|
message_label.setText(message) |
||||||
|
|
||||||
|
message_dialog.exec_() |
||||||
|
|
||||||
|
|
||||||
|
class SettingsController: |
||||||
|
def __init__(self, main_window_ui: MainWindow.Ui_mainWindow): |
||||||
|
# region General |
||||||
|
json_string = DataStorage.read_json_file(SETTINGS_FILE_DIR + SETTINGS_FILE_NAME) |
||||||
|
json_dict = json.loads(json_string) |
||||||
|
self.settings = SettingsModel.from_json(json_dict) |
||||||
|
# endregion |
||||||
|
|
||||||
|
# region Reports |
||||||
|
self.yearly_dir_edit = main_window_ui.yearly_directory_edit |
||||||
|
self.other_dir_edit = main_window_ui.other_directory_edit |
||||||
|
|
||||||
|
main_window_ui.yearly_directory_button.clicked.connect( |
||||||
|
lambda: self.open_file_select_dialog(Setting.YEARLY_DIR)) |
||||||
|
main_window_ui.other_directory_button.clicked.connect( |
||||||
|
lambda: self.open_file_select_dialog(Setting.OTHER_DIR)) |
||||||
|
|
||||||
|
self.yearly_dir_edit.setText(self.settings.yearly_directory) |
||||||
|
self.other_dir_edit.setText(self.settings.other_directory) |
||||||
|
main_window_ui.request_interval_spin_box.setValue(self.settings.request_interval) |
||||||
|
main_window_ui.request_timeout_spin_box.setValue(self.settings.request_timeout) |
||||||
|
main_window_ui.concurrent_vendors_spin_box.setValue(self.settings.concurrent_vendors) |
||||||
|
main_window_ui.concurrent_reports_spin_box.setValue(self.settings.concurrent_reports) |
||||||
|
main_window_ui.empty_cell_edit.setText(self.settings.empty_cell) |
||||||
|
|
||||||
|
self.yearly_dir_edit.textEdited.connect( |
||||||
|
lambda text: self.on_setting_changed(Setting.YEARLY_DIR, text)) |
||||||
|
self.other_dir_edit.textEdited.connect( |
||||||
|
lambda text: self.on_setting_changed(Setting.OTHER_DIR, text)) |
||||||
|
main_window_ui.request_interval_spin_box.valueChanged.connect( |
||||||
|
lambda value: self.on_setting_changed(Setting.REQUEST_INTERVAL, value)) |
||||||
|
main_window_ui.request_timeout_spin_box.valueChanged.connect( |
||||||
|
lambda value: self.on_setting_changed(Setting.REQUEST_TIMEOUT, value)) |
||||||
|
main_window_ui.concurrent_vendors_spin_box.valueChanged.connect( |
||||||
|
lambda value: self.on_setting_changed(Setting.CONCURRENT_VENDORS, value)) |
||||||
|
main_window_ui.concurrent_reports_spin_box.valueChanged.connect( |
||||||
|
lambda value: self.on_setting_changed(Setting.CONCURRENT_REPORTS, value)) |
||||||
|
main_window_ui.empty_cell_edit.textEdited.connect( |
||||||
|
lambda text: self.on_setting_changed(Setting.EMPTY_CELL, text)) |
||||||
|
# endregion |
||||||
|
|
||||||
|
# region Reports Help Messages |
||||||
|
main_window_ui.yearly_directory_help_button.clicked.connect( |
||||||
|
lambda: show_message("This is where yearly files will be saved by default")) |
||||||
|
main_window_ui.other_directory_help_button.clicked.connect( |
||||||
|
lambda: show_message("This is where special and non-yearly files will be saved by default")) |
||||||
|
main_window_ui.request_interval_help_button.clicked.connect( |
||||||
|
lambda: show_message("The amount of time to wait between a vendor's report requests")) |
||||||
|
main_window_ui.request_timeout_help_button.clicked.connect( |
||||||
|
lambda: show_message("The amount of time to wait before cancelling a request")) |
||||||
|
main_window_ui.concurrent_vendors_help_button.clicked.connect( |
||||||
|
lambda: show_message("The maximum number of vendors to work on at the same time")) |
||||||
|
main_window_ui.concurrent_reports_help_button.clicked.connect( |
||||||
|
lambda: show_message("The maximum number of reports to work on at the same time, per vendor")) |
||||||
|
main_window_ui.empty_cell_help_button.clicked.connect( |
||||||
|
lambda: show_message("Empty cells will be replaced by whatever is in here")) |
||||||
|
# endregion |
||||||
|
|
||||||
|
def on_setting_changed(self, setting: Setting, setting_value): |
||||||
|
if setting == Setting.YEARLY_DIR: |
||||||
|
self.settings.yearly_directory = setting_value |
||||||
|
elif setting == Setting.OTHER_DIR: |
||||||
|
self.settings.other_directory = setting_value |
||||||
|
elif setting == Setting.REQUEST_INTERVAL: |
||||||
|
self.settings.request_interval = int(setting_value) |
||||||
|
elif setting == Setting.REQUEST_TIMEOUT: |
||||||
|
self.settings.request_timeout = int(setting_value) |
||||||
|
elif setting == Setting.CONCURRENT_VENDORS: |
||||||
|
self.settings.concurrent_vendors = int(setting_value) |
||||||
|
elif setting == Setting.CONCURRENT_REPORTS: |
||||||
|
self.settings.concurrent_reports = int(setting_value) |
||||||
|
elif setting == Setting.EMPTY_CELL: |
||||||
|
self.settings.empty_cell = setting_value |
||||||
|
|
||||||
|
self.save_settings_to_disk() |
||||||
|
|
||||||
|
def open_file_select_dialog(self, setting: Setting): |
||||||
|
dialog = QFileDialog() |
||||||
|
dialog.setFileMode(QFileDialog.Directory) |
||||||
|
if dialog.exec_(): |
||||||
|
directory = dialog.selectedFiles()[0] + "/" |
||||||
|
if setting == Setting.YEARLY_DIR: |
||||||
|
self.yearly_dir_edit.setText(directory) |
||||||
|
elif setting == Setting.OTHER_DIR: |
||||||
|
self.other_dir_edit.setText(directory) |
||||||
|
|
||||||
|
self.on_setting_changed(setting, directory) |
||||||
|
|
||||||
|
def save_settings_to_disk(self): |
||||||
|
json_string = json.dumps(self.settings, default=lambda o: o.__dict__) |
||||||
|
DataStorage.save_json_file(SETTINGS_FILE_DIR, SETTINGS_FILE_NAME, json_string) |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@ -0,0 +1,222 @@ |
|||||||
|
[ |
||||||
|
{ |
||||||
|
"name": "123", |
||||||
|
"customer_id": "", |
||||||
|
"base_url": "", |
||||||
|
"requestor_id": "", |
||||||
|
"api_key": "", |
||||||
|
"platform": "", |
||||||
|
"is_local": true, |
||||||
|
"description": "123", |
||||||
|
"companies": "" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "AMS-JOURNALS-BOOKS", |
||||||
|
"customer_id": "UPEIS*CHA", |
||||||
|
"base_url": "https://counter.ams.org/counter/r5/reports", |
||||||
|
"requestor_id": "", |
||||||
|
"api_key": "x7r2w8s5=", |
||||||
|
"platform": "", |
||||||
|
"is_local": false, |
||||||
|
"description": "", |
||||||
|
"companies": "" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "AMS-MATHSCINET", |
||||||
|
"customer_id": "UPEIS*CHA", |
||||||
|
"base_url": "https://mathscinet.ams.org/counter/r5/reports", |
||||||
|
"requestor_id": "", |
||||||
|
"api_key": "x7r2w8s5", |
||||||
|
"platform": "", |
||||||
|
"is_local": false, |
||||||
|
"description": "", |
||||||
|
"companies": "" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "ANNUAL-REVIEWS", |
||||||
|
"customer_id": "106712", |
||||||
|
"base_url": "https://www.annualreviews.org/reports", |
||||||
|
"requestor_id": "upei", |
||||||
|
"api_key": "", |
||||||
|
"platform": "", |
||||||
|
"is_local": false, |
||||||
|
"description": "", |
||||||
|
"companies": "" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "AVMA-JOURNALS", |
||||||
|
"customer_id": "175152", |
||||||
|
"base_url": "https://avmajournals.avma.org/reports", |
||||||
|
"requestor_id": "collections@upei.ca", |
||||||
|
"api_key": "", |
||||||
|
"platform": "", |
||||||
|
"is_local": false, |
||||||
|
"description": "", |
||||||
|
"companies": "" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "BIOONE", |
||||||
|
"customer_id": "512B6680-A058-E811-80C8-005056B9C586", |
||||||
|
"base_url": "https://sushi5.scholarlyiq.com/counter/r5/reports", |
||||||
|
"requestor_id": "eafe8b88-2d7e-4a96-9319-e13e10598817", |
||||||
|
"api_key": "", |
||||||
|
"platform": "", |
||||||
|
"is_local": false, |
||||||
|
"description": "", |
||||||
|
"companies": "" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "BMJ", |
||||||
|
"customer_id": "396291", |
||||||
|
"base_url": "https://sushi5.scholarlyiq.com/counter/r5/reports", |
||||||
|
"requestor_id": "a4f4efe1-bbf8-44f0-90b7-c1442964bff9", |
||||||
|
"api_key": "", |
||||||
|
"platform": "", |
||||||
|
"is_local": false, |
||||||
|
"description": "", |
||||||
|
"companies": "" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "CAMBRIDGE", |
||||||
|
"customer_id": "8a94032b5413f999015414009bdc0", |
||||||
|
"base_url": "https://event-aggregation-api-prod-platform.prod.aop.cambridge.org/reports", |
||||||
|
"requestor_id": "8a94032b5413f999015414009bdc0d1c", |
||||||
|
"api_key": "", |
||||||
|
"platform": "", |
||||||
|
"is_local": false, |
||||||
|
"description": "", |
||||||
|
"companies": "" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "EBSCO", |
||||||
|
"customer_id": "uprince", |
||||||
|
"base_url": "https://sushi.ebscohost.com/R5/reports", |
||||||
|
"requestor_id": "c456a677-df9c-415d-9a98-168170d7a427", |
||||||
|
"api_key": "", |
||||||
|
"platform": "", |
||||||
|
"is_local": false, |
||||||
|
"description": "", |
||||||
|
"companies": "" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "Emerald", |
||||||
|
"customer_id": "2182277", |
||||||
|
"base_url": "https://connect.liblynx.com/sushi/r5/reports", |
||||||
|
"requestor_id": "021f71b6-a210-4080-8664-1c305d51c632", |
||||||
|
"api_key": "021f71b6-a210-4080-8664-1c305d51c632", |
||||||
|
"platform": "", |
||||||
|
"is_local": false, |
||||||
|
"description": "Test description\nThat we are testing", |
||||||
|
"companies": "" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "HIGHWIRE", |
||||||
|
"customer_id": "collections@upei.ca", |
||||||
|
"base_url": "https://hwdpapi.highwire.org/sushi/reports", |
||||||
|
"requestor_id": "", |
||||||
|
"api_key": "collections@upei.ca|r0bertson1", |
||||||
|
"platform": "", |
||||||
|
"is_local": false, |
||||||
|
"description": "", |
||||||
|
"companies": "" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "OVID", |
||||||
|
"customer_id": "4391", |
||||||
|
"base_url": "https://stats.ovid.com/C5/sushi/reports", |
||||||
|
"requestor_id": "9295320c-2719-228d-5414-0185e45dd518", |
||||||
|
"api_key": "", |
||||||
|
"platform": "", |
||||||
|
"is_local": false, |
||||||
|
"description": "", |
||||||
|
"companies": "" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "OXFORD-BOOKS", |
||||||
|
"customer_id": "13742", |
||||||
|
"base_url": "https://sushi5.scholarlyiq.com/counter/r5/reports", |
||||||
|
"requestor_id": "afbe0d71-d7c5-463f-96eb-859827823006", |
||||||
|
"api_key": "", |
||||||
|
"platform": "", |
||||||
|
"is_local": false, |
||||||
|
"description": "", |
||||||
|
"companies": "" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "PHILOSOPHY-DOCUMENTATION-CENTRE-(PDC)", |
||||||
|
"customer_id": "4154946", |
||||||
|
"base_url": "https://sushi5.scholarlyiq.com/counter/r5/reports", |
||||||
|
"requestor_id": "1034f0d0-c887-49a9-bdbf-01d15d11e80a", |
||||||
|
"api_key": "", |
||||||
|
"platform": "", |
||||||
|
"is_local": false, |
||||||
|
"description": "", |
||||||
|
"companies": "" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "PROJECT-MUSE", |
||||||
|
"customer_id": "i16692", |
||||||
|
"base_url": "https://about.muse.jhu.edu/lib/counter5/sushi/reports", |
||||||
|
"requestor_id": "", |
||||||
|
"api_key": "e22055247325aadbbeca049983fad6c3a3497", |
||||||
|
"platform": "", |
||||||
|
"is_local": false, |
||||||
|
"description": "", |
||||||
|
"companies": "test1234" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "PROQUEST", |
||||||
|
"customer_id": "14670", |
||||||
|
"base_url": "https://sushi.proquest.com/counter/r5/reports", |
||||||
|
"requestor_id": "", |
||||||
|
"api_key": "DG9OBF7RMZAPFJSH", |
||||||
|
"platform": "", |
||||||
|
"is_local": false, |
||||||
|
"description": "", |
||||||
|
"companies": "" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "RSC", |
||||||
|
"customer_id": "3140", |
||||||
|
"base_url": "https://c5sushi.mpsinsight.com/c5sushi/services/reports", |
||||||
|
"requestor_id": "", |
||||||
|
"api_key": "rsc::eeb5bf9677c6f2ec2bf3102e5d3bacd1", |
||||||
|
"platform": "", |
||||||
|
"is_local": false, |
||||||
|
"description": "", |
||||||
|
"companies": "" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "SAGE-JOURNALS", |
||||||
|
"customer_id": "10004515", |
||||||
|
"base_url": "https://journals.sagepub.com/reports", |
||||||
|
"requestor_id": "mbelvadi@upei.ca", |
||||||
|
"api_key": "", |
||||||
|
"platform": "", |
||||||
|
"is_local": false, |
||||||
|
"description": "", |
||||||
|
"companies": "" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "UNIVERSITY-OF-CHICAGO", |
||||||
|
"customer_id": "117870", |
||||||
|
"base_url": "https://www.journals.uchicago.edu/reports", |
||||||
|
"requestor_id": "collections@upei.ca", |
||||||
|
"api_key": "", |
||||||
|
"platform": "", |
||||||
|
"is_local": false, |
||||||
|
"description": "", |
||||||
|
"companies": "" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "UNIVERSITY-OF-TORONTO-PRESS", |
||||||
|
"customer_id": "101005", |
||||||
|
"base_url": "https://www.utpjournals.press/reports", |
||||||
|
"requestor_id": "upei", |
||||||
|
"api_key": "", |
||||||
|
"platform": "", |
||||||
|
"is_local": false, |
||||||
|
"description": "", |
||||||
|
"companies": "" |
||||||
|
} |
||||||
|
] |
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue