Exemplo n.º 1
0
class SetupMain(VerticalScrolledFrame):
    """The main class for the GUI of the application"""

    setup = None
    """This is the merge object of the application, it holds all settings for the merge operation"""
    setup_filename = None
    """The name of the file containing the merge definition"""
    fr_src_dataset = None
    """The fram of the source dataset, contains a FrameCustomDataset descendant"""
    fr_dest_dataset = None
    """The fram of the source dataset, contains a FrameCustomDataset descendant"""
    suppress_errors = None
    """Do not show any errors"""
    _row_index = None
    """The current row in the dataset"""
    curr_mapping_frame = None
    """The currently selected mapping frame"""

    def __init__(self, _setup=None, _setup_filename=None, *args, **kw):

        self.parent = Tk()

        # Init oneself

        super(SetupMain, self).__init__(self.parent, bd=1, relief=SUNKEN, *args, **kw)
        self.grid(stick=(E, W, N, S))

        self.suppress_errors = None

        self.setup = _setup
        self.setup_filename = StringVar()
        self.setup_filename.set(_setup_filename)

        self.install_location = None
        self.plugins_location = None
        self.fr_settings = None

        self.fr_dest_dataset = None

        self.grid()
        self.ip_address = StringVar()
        self._row_index = 0
        self.init_GUI()

        if _setup_filename is not None and _setup is not None:
            # _merge._load_datasets()
            self._setup_to_gui()

        self.parent.columnconfigure(0, weight=1)
        self.parent.rowconfigure(0, weight=1)
        self.resize()

        self.parent.mainloop()

    def resize(self):
        """
        Resize the window, set the width what the internal windows need.
        """
        self._canvas.update_idletasks()
        self.fr_top_right.update_idletasks()
        self._canvas.config(width=self.interior.winfo_reqwidth() + 1, height=self.interior.winfo_reqheight())

    def on_dataset_columns_change(self, *args):
        # Columns have changed; force reload columns from structure
        self.fr_src_dataset.get_possible_references(True)
        self.fr_dest_dataset.get_possible_references(True)
        for curr_mapping in self.g_plugins.items:
            curr_mapping.fr_item.reload_references()

    def notify_task(self, _task, _progress):
        """Override as this is the top widget"""
        self.fr_Status_Bar.update_task(_task, _progress)

    def notify_messagebox(self, _title, _message, _kind=None):
        """Override as this is the top class, default is error."""
        if self.suppress_errors is None:
            if _kind == "message":
                messagebox.showinfo(_title, _message)
            elif _kind == "warning":
                messagebox.showwarning(_title, _message)
            else:
                messagebox.showerror(_title, _message)

    def on_post_merge_sql(self, *args):
        # Show post-merge-SQL dialog
        _wdw = Toplevel()
        _wdw.geometry("+400+400")
        _wdw.e = TextExtension(_wdw, textvariable=self.post_execute_sql)
        _wdw.e.pack()
        _wdw.e.focus_set()
        _wdw.transient(self.parent)
        _wdw.grab_set()
        self.parent.wait_window(_wdw)
        _wdw.e.unhook()
        del (_wdw)

    def on_select_plugin_folder(self, *args):
        _plugin_folder = filedialog.askdirectory(title="Choose plugin folder (usually in the ")
        if _plugin_folder is not None:
            self.plugins_location.set(_plugin_folder)

    def on_select_install_folder(self, *args):
        _install_folder = filedialog.askdirectory(title='Choose install folder (usually in the "~of"-folder)')
        if _install_folder is not None:
            self.install_location.set(_install_folder)

    def on_select_installation(self, *args):
        _install_folder = filedialog.askdirectory(title="Select existing installation")
        if _install_folder is not None:
            self.install_location.set(_install_folder)
            self.setup.load_install(_install_folder=_install_folder)
            self._setup_to_gui()

    def init_GUI(self):
        """Init main application GUI"""
        print("Initializing GUI...", end="")

        self.parent.title("Optimal Framework setup")
        self.interior.notify_task = self.notify_task
        self.interior.notify_messagebox = self.notify_messagebox

        self.fr_top = BaseFrame(self.interior)
        self.fr_top.pack(side=TOP, fill=BOTH, expand=1)

        self.fr_top_left = BaseFrame(self.fr_top)
        self.fr_top_left.pack(side=LEFT, fill=BOTH, expand=1)

        self.fr_rw = BaseFrame(self.fr_top_left)
        self.fr_rw.pack(side=TOP, fill=X)

        self.btn_load_json_json = ttk.Button(self.fr_rw, text="Load", command=self.on_load_json)
        self.btn_load_json_json.pack(side=LEFT)
        self.btn_save_json = ttk.Button(self.fr_rw, text="Save", command=self.on_save_json)
        self.btn_save_json.pack(side=LEFT)
        self.btn_load_folder = ttk.Button(self.fr_rw, text="Existing", command=self.on_select_installation)
        self.btn_load_folder.pack(side=LEFT)
        self.fr_setup_filename = BaseFrame(self.fr_rw)
        self.l_setup_filename = ttk.Label(self.fr_setup_filename, text="Setup file:")
        self.l_setup_filename.pack(side=LEFT)
        self.setup_filename.set("")
        self.e_config_filename = ttk.Entry(self.fr_setup_filename, textvariable=self.setup_filename)
        self.e_config_filename.pack(side=RIGHT)

        self.fr_setup_filename.pack(side=RIGHT)

        # Settings

        self.fr_settings = BaseFrame(self.fr_top_left)
        self.fr_settings.pack(side=TOP, fill=BOTH)
        self.fr_settings.columnconfigure(2, weight=5)
        self.install_location, self.l_install_location, self.e_install_location, self.b_install_location = make_entry(
            self.fr_settings, "Install location:", 0, _button_caption=".."
        )
        self.b_install_location.config(command=self.on_select_install_folder)
        self.plugins_location, self.l_plugin_location, self.e_plugin_location, self.b_plugin_location = make_entry(
            self.fr_settings, "Plugin location:", 1, _button_caption=".."
        )
        self.b_plugin_location.config(command=self.on_select_plugin_folder)
        self.install_repository_url, self.l_install_repository_url, self.e_install_repository_url = make_entry(
            self.fr_settings, "install location:", 2
        )

        # Plugins

        self.fr_plugins_header = BaseFrame(self.fr_top_left)
        self.fr_plugins_header.pack(side=TOP)

        self.l_plugins = ttk.Label(self.fr_plugins_header, text="Plugins:")
        self.l_plugins.pack(side=TOP)

        self.fr_plugins_header_nav = BaseFrame(self.fr_plugins_header)
        self.fr_plugins_header_nav.pack(side=BOTTOM)

        self.btn_reload = Button(self.fr_plugins_header_nav, text="Refresh", command=self.on_refresh_plugins)
        self.btn_reload.pack(side=LEFT)

        self.g_plugins = FrameList(self.fr_top_left, _detail_key_text="Plugins >>", bd=1, relief=SUNKEN)
        self.g_plugins.pack(side=TOP, fill=X)
        # self.g_plugins.on_delete = self.plugins_do_on_delete
        # self.g_plugins.on_detail = self.plugins_do_on_detail

        self.btn_append_mapping = Button(self.fr_top_left, text="Append mapping", command=self.on_append_mapping)
        self.btn_append_mapping.pack(side=TOP)

        # Plugin details
        self.fr_top_right = BaseFrame(self.fr_top)
        self.fr_top_right.pack(side=RIGHT, fill=Y)

        self.l_plugin = ttk.Label(self.fr_top_right, text="Plugin details")
        self.l_plugin.pack(side=TOP)
        self.fr_plugin = BaseFrame(self.fr_top_right, bd=1, relief=SUNKEN)
        self.fr_plugin.pack(side=TOP, fill=BOTH, expand=1)
        self.plugin_url = make_entry(self.fr_plugin, "Repository URL(http):", 0)

        # Merge preview
        self.fr_Preview = ttk.Frame(self.fr_top_left)
        self.fr_Preview.pack(side=TOP, fill=BOTH, expand=1)

        self.fr_merge_actions = ttk.Frame(self.fr_Preview)
        self.fr_merge_actions.pack(side=TOP, fill=X)

        self.btn_execute_preview = Button(self.fr_merge_actions, text="Preview merge", command=self.on_preview_merge)
        self.btn_execute_preview.pack(side=LEFT)
        self.btn_execute_preview = Button(self.fr_merge_actions, text="Commit merge", command=self.on_commit_merge)
        self.btn_execute_preview.pack(side=LEFT)

        # Update
        self.merge_update = BooleanVar()
        self.e_merge_update = ttk.Checkbutton(self.fr_merge_actions, variable=self.merge_update)
        self.e_merge_update.pack(side=RIGHT)
        self.l_merge_update = ttk.Label(self.fr_merge_actions, text="Update: ")
        self.l_merge_update.pack(side=RIGHT)

        # Insert

        self.merge_insert = BooleanVar()
        self.e_merge_insert = ttk.Checkbutton(self.fr_merge_actions, variable=self.merge_insert)
        self.e_merge_insert.pack(side=RIGHT)
        self.l_merge_insert = ttk.Label(self.fr_merge_actions, text="Insert: ")
        self.l_merge_insert.pack(side=RIGHT)

        # Delete
        self.merge_delete = BooleanVar()
        self.e_merge_delete = ttk.Checkbutton(self.fr_merge_actions, variable=self.merge_delete)
        self.e_merge_delete.pack(side=RIGHT)
        self.l_merge_delete = ttk.Label(self.fr_merge_actions, text="Delete: ")
        self.l_merge_delete.pack(side=RIGHT)

        # Set post-merge SQL
        self.post_execute_sql = StringVar()
        self.btn_Post_Merge_SQL = ttk.Button(
            self.fr_merge_actions, text="Set post-merge SQL", command=self.on_post_merge_sql
        )
        self.btn_Post_Merge_SQL.pack(side=RIGHT, padx=30)

        # Preview
        self.gr_preview = ttk.Treeview(self.fr_Preview, columns=("size", "modified"))
        self.gr_preview.pack(side=TOP, fill=BOTH, expand=1)
        self.gr_preview.bind("<<TreeviewSelect>>", self.on_preview_selected)
        self.preview_detail = StringVar()
        self.e_previev_detail = ttk.Entry(self.fr_Preview, textvariable=self.preview_detail)
        self.e_previev_detail.pack(side=BOTTOM, fill=X, expand=0)

        self.fr_bottom = BaseFrame(self.interior)
        self.fr_bottom.pack(side=BOTTOM, fill=X)

        self.fr_Status_Bar = Status_Bar(self.fr_bottom)
        self.fr_Status_Bar.pack(fill=X)

        print("done.")

    # #########################################################################
    # This section contains functions handling the entire setup(load/save/GUI)
    # #########################################################################

    def plugins_to_gui(self):
        # clear plugin list

        # populate with plugins
        for _curr_plugin in self.setup.plugins:
            print(_curr_plugin)

        self.g_plugins.clear()
        for _curr_plugin in self.setup.plugins:
            _new_item = self.g_plugins.append_item()
            _new_item.make_item(_class=FramePlugin, _plugin=_curr_plugin)

    def _setup_to_gui(self):
        """
        Populate the GUI from the setup class.
        """

        self.install_location.set(self.setup.install_location)
        self.plugins_location.set(self.setup.plugins_location)
        self.plugins_to_gui()
        """
        if self.fr_src_dataset is not None:
            self.fr_src_dataset.destroy()
        _src_type = self.dataset_instance_to_dataset_type(self.setup.source)
        self.sel_src_dataset_type.set_but_do_not_propagate(_src_type)
        self.fr_src_dataset = self.dataset_frame_factory(_dataset=self.setup.source, _is_destination=False)
        self.fr_src_dataset.grid(column=0, row=1)

        if self.fr_dest_dataset is not None:
            self.fr_dest_dataset.destroy()

        _dest_type = self.dataset_instance_to_dataset_type(self.setup.destination)
        self.sel_dest_dataset_type.set_but_do_not_propagate(_dest_type)
        self.fr_dest_dataset = self.dataset_frame_factory(_dataset=self.setup.destination, _is_destination=False)
        self.fr_dest_dataset.grid(column=1, row=1)

        self.mappings_to_gui()

        self.merge_insert.set(bool_to_binary_int(self.setup.insert))
        self.merge_delete.set(bool_to_binary_int(self.setup.delete))
        self.merge_update.set(bool_to_binary_int(self.setup.update))
        if self.setup.post_execute_sql is None:
            self.post_execute_sql.set("")
        else:
            self.post_execute_sql.set(self.setup.post_execute_sql)

        # Hereafter, update column list when they change
        self.fr_src_dataset.on_columns_change = self.on_dataset_columns_change
        self.fr_dest_dataset.on_columns_change = self.on_dataset_columns_change
        """

    def _gui_to_merge(self):
        """Copy the data from the GUI to the merge object"""
        self.fr_src_dataset.write_to_dataset()
        self.setup.source = self.fr_src_dataset.dataset
        self.fr_dest_dataset.write_to_dataset()
        self.setup.destination = self.fr_dest_dataset.dataset

        self.gui_to_mappings()

        self.setup.insert = binary_int_to_bool(self.merge_insert.get())
        self.setup.delete = binary_int_to_bool(self.merge_delete.get())
        self.setup.update = binary_int_to_bool(self.merge_update.get())
        self.setup.post_execute_sql = self.post_execute_sql.get()

    def load_json(self, _filename):
        """Load an JSON into the merge object, and populate the GUI"""
        with open(_filename, "r") as _f:
            _json = json.load(_f)

        self.setup_filename = _filename

        self.notify_task("Loading transformation..", 0)
        self.setup = Merge(_json=_json, _base_path=os.path.dirname(_filename))
        try:
            self.setup._load_datasets()
        except Exception as e:
            self.notify_messagebox("Error loading data", str(e))
            # Supress the following errors. There is no real errors that matters.
            self.suppress_errors = True
        self._config_to_gui()
        self.suppress_errors = None
        self.notify_task("Loading transformation..done", 100)
        self.resize()

    def on_save_json(self, *args):
        """Triggered when save-button is clicked.
        Displays a save dialog, fetches GUI data into merge, and saves as JSON into the selected file."""
        self.notify_task("Saving..", 0)
        _filename = filedialog.asksaveasfilename(
            initialfile=self.setup_filename,
            defaultextension=".json",
            filetypes=[("JSON files", ".json"), ("all files", ".*")],
            title="Choose location",
        )
        if _filename:
            self._gui_to_merge()
            self.notify_task("Saving(Generating JS)..", 0)
            _json = self.setup.as_json()
            self.notify_task("Saving(Writing file)..", 50)
            with open(_filename, "w") as _f:
                json.dump(_json, fp=_f, sort_keys=True, indent=4)

            self.notify_task("Saving..done.", 100)
        else:
            self.notify_task("Saving cancelled.", 0)

    def on_load_json(self, *args):
        """Triggered when load-button is clicked.
        Displays a load dialog, clears the GUI, populates the merge and uppdates the GUI"""
        _filename = filedialog.askopenfilename(
            defaultextension=".json", filetypes=[("JSON files", ".json"), ("all files", ".*")], title="Choose file"
        )
        if _filename:
            self.g_transformations.clear()
            self.g_plugins.clear()
            self.clear_preview()
            self.curr_mapping_frame = None
            self._row_index = 0
            self.load_json(_filename)

    def check_prerequisites_for_reload(self):
        """Can a reload be made using the current settings? If not, display cause in status field"""

        if self.fr_src_dataset is None:
            self.notify_task("Cannot reload: Source dataset must be specified.", 0)
            return False
        elif self.fr_dest_dataset is None:
            self.notify_task("Cannot reload: Destination dataset must be specified.", 0)
            return False
        _tmp = self.fr_src_dataset.check_reload()
        if _tmp:
            self.notify_task("Cannot reload source: " + _tmp, 0)
            return False
        _tmp = self.fr_dest_dataset.check_reload()
        if _tmp:
            self.notify_task("Cannot reload destination: " + _tmp, 0)
            return False
        else:
            return True

    def update_data(self, _refresh=None):
        """
        Reload all data into the GUI
        :param _refresh: Force reload of datasets
        :return:
        """

        if self.check_prerequisites_for_reload() is False:
            return

        self.notify_task("", 0)
        if len(self.setup.source.data_table) == 0 or _refresh:
            # Update settings
            self._gui_to_merge()

            # Update XPath references especially, since it addresses an XML structure, not a dataset.
            if isinstance(self.setup.source, XpathDataset):
                self.setup.source.field_xpaths = []
                self.setup.source.field_names = []
                for _curr_mapping_idx in range(0, len(self.g_plugins.items)):
                    self.setup.source.field_xpaths.append(
                        self.g_plugins.items[_curr_mapping_idx].fr_item.src_reference.get()
                    )
                    self.setup.source.field_names.append(
                        self.g_plugins.items[_curr_mapping_idx].fr_item.src_reference.get()
                    )

            self.setup.source.load()
        # Reset identity values
        self.reset_substitions_identity()
        # Is there any data?
        if len(self.setup.source.data_table) > 0:
            # Try to retain the approximate position in the table.
            if self._row_index < 0:
                self._row_index = 0
            elif self._row_index > len(self.setup.source.data_table) - 1:
                self._row_index = len(self.setup.source.data_table) - 1
            # Loop through mappings, update data and perform transformations
            # TODO: This certainly doesn't seem to belong here, should be extracted
            for _curr_mapping_idx in range(0, len(self.g_plugins.items)):
                _curr_frame = self.g_plugins.items[_curr_mapping_idx].fr_item
                _curr_frame.hide_error()
                _src_ref = _curr_frame.src_reference.get()
                try:
                    if isinstance(self.setup.source, XpathDataset):
                        _col_idx = self.setup.source.field_xpaths.index(_src_ref)
                    else:
                        _col_idx = self.setup.source.field_names.index(_src_ref)
                except ValueError:
                    _col_idx = -1

                if _col_idx > -1:
                    _curr_frame.curr_raw_data = self.setup.source.data_table[self._row_index][_col_idx]
                    try:
                        perform_transformations(
                            _input=_curr_frame.curr_raw_data, _transformations=_curr_frame.mapping.transformations
                        )
                    except Exception as e:
                        self.notify_task(
                            "Error in one of the transformations, mapping: " + _src_ref + " error: " + str(e), 0
                        )

                    _curr_frame.curr_data.set(str(_curr_frame.curr_raw_data))

                else:
                    _curr_frame.show_error(_msg="No mapping")
                    _curr_frame.curr_data.set("")

                    try:
                        _curr_frame.curr_raw_data = perform_transformations(
                            _input=None, _transformations=_curr_frame.mapping.transformations
                        )
                    except Exception as e:
                        self.notify_task(
                            "Error in one of the transformations, mapping: "
                            + _curr_frame.dest_reference.get()
                            + " error: "
                            + str(e),
                            0,
                        )

                    _curr_frame.curr_data.set(str(_curr_frame.curr_raw_data))

                self.g_plugins.items[_curr_mapping_idx].fr_item.reload_references()

    # #########################################################
    # The following events deals with navigating the active dataset
    ##########################################################

    def on_refresh_plugins(self):
        """Triggered when the "Reload data"-button is pressed."""
        self.refresh_plugins()

    def dataset_frame_factory(self, _dataset=None, _dataset_type=None, _is_destination=False):
        """
        This is a factory function for creating matching frames(visual property editors) for the dataset classes.
        :param _dataset: The dataset, if existing.
        :param _dataset_type: The dataset type string representation ("RDBMS", and so on)
        """
        if _dataset:
            _dataset_type = self.dataset_instance_to_dataset_type(_dataset)

        if _dataset_type == "RDBMS":
            _tmp = FrameRDBMSDataset(
                self.fr_settings, _dataset=_dataset, _relief=SUNKEN, _is_destination=_is_destination
            )
            _tmp.subnet_ip = self.ip_address
        elif _dataset_type == "FLATFILE":
            _tmp = FrameFlatfileDataset(
                self.fr_settings, _dataset=_dataset, _relief=SUNKEN, _is_destination=_is_destination
            )
        elif _dataset_type == "XPATH":
            _tmp = FrameXPathDataset(
                self.fr_settings, _dataset=_dataset, _relief=SUNKEN, _is_destination=_is_destination
            )
        elif _dataset_type == "SPREADSHEET":
            _tmp = FrameSpreadsheetDataset(
                self.fr_settings, _dataset=_dataset, _relief=SUNKEN, _is_destination=_is_destination
            )
        else:
            raise Exception("Internal error, unsupported dataset type: " + str(_dataset_type))
        if self.setup_filename is not None:
            _tmp.base_path = os.path.dirname(self.setup_filename)
        return _tmp

    def on_src_dataset_type_change(self, _current_value):
        """
        Triggered when a user selects a different dataset type for the source dataset
        :param _current_value: A string describing what dataset type has been selected.
        """
        if self.fr_src_dataset is not None:
            self.fr_src_dataset.destroy()
        self.fr_src_dataset = self.dataset_frame_factory(_dataset_type=_current_value.upper(), _is_destination=False)
        self.setup.source = self.fr_src_dataset.dataset
        self.fr_src_dataset.grid(column=0, row=1)

    def on_dest_dataset_type_change(self, _current_value):
        """
        Triggered when a user selects a different dataset type for the destination dataset
        :param _current_value: A string describing what dataset type has been selected.
        """
        if self.fr_dest_dataset is not None:
            self.fr_dest_dataset.destroy()
        self.fr_dest_dataset = self.dataset_frame_factory(_dataset_type=_current_value.upper(), _is_destination=True)
        self.setup.destination = self.fr_dest_dataset.dataset
        self.fr_dest_dataset.grid(column=1, row=1)

    def get_source_references(self, _force=None):
        """
        Returns the possible field references from the source dataset
        :param _force: If True, forces a reload of the underlying dataset.
        """
        if self.fr_src_dataset is not None:
            try:
                return self.fr_src_dataset.get_possible_references(_force)
            except Exception as e:
                self.notify_messagebox(
                    _title="Failed refreshing source references", _message="Error: " + str(e), _kind="warning"
                )
                return []

    def get_destination_references(self, _force=None):
        """
        Returns the possible field references from the destination dataset
        :param _force: If True, forces a reload of the underlying dataset.
        """
        if self.fr_dest_dataset is not None:
            try:
                return self.fr_dest_dataset.get_possible_references(_force)
            except Exception as e:
                self.notify_messagebox(
                    _title="Failed refreshing destination references", _message="Error: " + str(e), _kind="warning"
                )
                return []

    ##########################################################
    # This section contains functions handling field mappings
    ##########################################################
    def mappings_to_gui(self):
        """Populates the GUI from the mappings list of the merge object"""

        self.g_plugins.clear()
        for _curr_mapping in self.setup.mappings:
            _new_item = self.g_plugins.append_item()
            _new_item.make_item(
                _class=FrameMapping,
                _mapping=_curr_mapping,
                _on_get_source_references=self.get_source_references,
                _on_get_destination_references=self.get_destination_references,
            )

    def gui_to_mappings(self):
        """Gathers data from GUI into the mappings list of the merge object"""

        self.gui_to_transformations()
        for _curr_mapping in self.g_plugins.items:
            _curr_mapping.fr_item.gui_to_mapping()

        self.setup._mappings_to_fields(self.setup.source, _use_dest=False)
        self.setup._mappings_to_fields(self.setup.destination, _use_dest=True)

    def mappings_do_on_delete(self, _g_plugins, _item_frame):
        """Triggered if the "del"-button has been clicked"""
        self.setup.mappings.remove(_item_frame.fr_item.mapping)

    def mappings_do_on_move_up(self, _g_plugins, _item_frame):
        """Triggered if the up arrow-button has been clicked"""
        _curr_idx = self.setup.mappings.index(_item_frame.fr_item.mapping)
        self.setup.mappings.insert(_curr_idx - 1, self.setup.mappings.pop(_curr_idx))

    def mappings_do_on_move_down(self, _g_plugins, _item_frame):
        """Triggered if the down arrow-button has been clicked"""
        _curr_idx = self.setup.mappings.index(_item_frame.fr_item.mapping)
        self.setup.mappings.insert(_curr_idx + 1, self.setup.mappings.pop(_curr_idx))

    def on_append_mapping(self, *args):
        """Triggered if the "Append mapping"-button has been clicked."""
        _new_mapping = Mapping()
        self.setup.mappings.append(_new_mapping)
        _new_item = self.g_plugins.append_item()
        _new_item.make_item(
            _class=FrameMapping,
            _mapping=_new_mapping,
            _on_get_source_references=self.get_source_references,
            _on_get_destination_references=self.get_destination_references,
        )

    def mappings_do_on_detail(self, _g_plugins, _item_frame):
        self.notify_task("", 0)
        if self.curr_mapping_frame:
            self.gui_to_transformations()
        self.g_transformations.clear()
        for _curr_transformation in _item_frame.fr_item.mapping.transformations:

            _frame_class = self._transformation_frame_class_lookup(_curr_transformation)
            if _frame_class:
                _new_item = self.g_transformations.append_item()
                _new_item.make_item(_class=_frame_class, _transformation=_curr_transformation)
        _item_frame["background"] = "dark grey"

        try:
            if _item_frame.fr_item.curr_raw_data is not None:
                perform_transformations(
                    _input=_item_frame.fr_item.curr_raw_data,
                    _transformations=_item_frame.fr_item.mapping.transformations,
                )
        except Exception as e:
            self.notify_task(
                "Error in one of the transformations, mapping: "
                + _item_frame.fr_item.mapping.src_reference
                + " error: "
                + str(e),
                0,
            )

        if self.curr_mapping_frame:
            try:
                self.curr_mapping_frame["background"] = self["background"]
            except Exception as e:
                raise Exception("Error setting background to: " + self["background"] + ":" + str(e))
        self.curr_mapping_frame = _item_frame

    ##########################################################
    # This section contains functions handling transformations
    ##########################################################
    def gui_to_transformations(self):
        """Gathers data from GUI into the transformation objects"""

        for _curr_transformation in self.g_transformations.items:
            _curr_transformation.fr_item.gui_to_transformation()

    def _transformation_frame_class_lookup(self, _transformation=None, _type=None):
        if _type is None:
            _type, _desc = transformation_to_type(_transformation)

        if _type == "Cast":
            return FrameTransformationCast
        if _type == "Trim":
            return FrameTransformationTrim
        if _type == "If empty":
            return FrameTransformationIfEmpty
        if _type == "Replace":
            return FrameTransformationReplace
        if _type == "Replace regex":
            return FrameTransformationReplaceRegex
        else:
            return None
            # raise Exception("Internal error, unsupported transformation type: " + str(_transformation_type))

    def transformations_do_on_delete(self, _g_transformations, _item_frame):
        self.curr_mapping_frame.fr_item.mapping.transformations.remove(_item_frame.fr_item.transformation)

    def transformations_do_on_move_up(self, _g_transformations, _item_frame):
        _curr_transformations = self.curr_mapping_frame.fr_item.mapping.transformations
        _curr_idx = _curr_transformations.index(_item_frame.fr_item.transformation)
        _curr_transformations.insert(_curr_idx - 1, _curr_transformations.pop(_curr_idx))

    def transformations_do_on_move_down(self, _g_transformations, _item_frame):
        _curr_transformations = self.curr_mapping_frame.fr_item.mapping.transformations
        _curr_idx = _curr_transformations.index(_item_frame.fr_item.transformation)
        _curr_transformations.insert(_curr_idx + 1, _curr_transformations.pop(_curr_idx))

    def on_append_transformation(self, *args):
        if self.curr_mapping_frame is not None:
            _new_transformation = type_to_transformation(self.sel_transformation_append_type.get())(
                _substitution=self.curr_mapping_frame.fr_item.mapping.substitution
            )
            self.curr_mapping_frame.fr_item.mapping.transformations.append(_new_transformation)
            _frame_class = self._transformation_frame_class_lookup(_new_transformation)
            if _frame_class:
                _new_item = self.g_transformations.append_item()
                _new_item.make_item(_class=_frame_class, _transformation=_new_transformation)

    def clear_transformation_events(self):
        for _curr_mapping in self.setup.mappings:
            for _curr_transformation in _curr_mapping.transformations:
                _curr_transformation.on_done = None

    ############################################################
    # This section contains functions handling the merge preview
    ############################################################

    def clear_preview(self):
        for _curr_item in self.gr_preview.get_children():
            self.gr_preview.delete(_curr_item)

    def reset_substitions_identity(self):
        """Reset substitions"""

        for _curr_mapping in self.setup.mappings:
            _curr_mapping.substitution.set_identity(0)

    def on_preview_merge(self, *args):
        self.do_merge(_commit=False)

    def on_commit_merge(self, *args):
        if (
            askokcancel(
                title="Warning: committing merge",
                message="This will commit actual changes to the destination, " "do you want to proceed?",
            )
            is True
        ):
            self.do_merge(_commit=True)

    def do_merge(self, _commit=False):
        self._gui_to_merge()
        self.update_data(_refresh=True)
        self.setup.destination_log_level = DATASET_LOGLEVEL_DETAIL
        # Clear GUI events
        self.clear_transformation_events()
        self.setup.clear_log()
        try:
            _data_table, _log, _deletes, _inserts, _updates = self.setup.execute(_commit=_commit)
        except Exception as e:
            self.notify_messagebox("Error while merging", str(e))
            return

        # Call columns src/dest field names if they differ

        if len(self.setup.key_fields) > 0:
            _key_field = self.setup.key_fields[0]
        else:
            _key_field = 0

        self.clear_preview()

        self.gr_preview["columns"] = ["Change_Data"]
        self.gr_preview.column("Change_Data", width=500)

        self.gr_preview.heading("Change_Data", text="Change/Data")

        # Add a main for each action

        # Add deletes
        self.gr_preview.insert(parent="", index="end", iid="obpm_deletes", text="Deletes")
        if self.setup.delete:
            for _curr_row in _deletes:
                _curr_item_idx = self.gr_preview.insert(
                    parent="obpm_deletes", index="end", iid="", text=_curr_row[2][_key_field]
                )
                _curr_value = ",".join([str(_item) for _item in _curr_row[2]])
                self.gr_preview.item(_curr_item_idx, values=[_curr_value])
                for _curr_column_idx in range(len(_curr_row[2])):
                    _curr_change_item_idx = self.gr_preview.insert(
                        parent=_curr_item_idx,
                        index="end",
                        iid="",
                        text=str(self.setup.destination.field_names[_curr_column_idx]),
                    )
                    self.gr_preview.item(_curr_change_item_idx, values=[str(_curr_row[2][_curr_column_idx])])
        # Add inserts
        self.gr_preview.insert(parent="", index="end", iid="obpm_inserts", text="Inserts")
        if self.setup.insert:
            for _curr_row in _inserts:
                _curr_item_idx = self.gr_preview.insert(
                    parent="obpm_inserts", index="end", iid="", text=_curr_row[2][_key_field]
                )
                _curr_value = ",".join([str(_item) for _item in _curr_row[2]])
                self.gr_preview.item(_curr_item_idx, values=[_curr_value])
                for _curr_column_idx in range(len(_curr_row[2])):
                    _curr_change_item_idx = self.gr_preview.insert(
                        parent=_curr_item_idx,
                        index="end",
                        iid="",
                        text=str(self.setup.destination.field_names[_curr_column_idx]),
                    )
                    self.gr_preview.item(_curr_change_item_idx, values=[str(_curr_row[2][_curr_column_idx])])
        # Add updates
        self.gr_preview.insert(parent="", index="end", iid="obpm_updates", text="Updates")
        if self.setup.update:
            for _curr_row in _updates:
                _curr_item_idx = self.gr_preview.insert(
                    parent="obpm_updates", index="end", iid="", text=_curr_row[2][_key_field]
                )
                _changes = []
                for _curr_column_idx in range(len(_curr_row[2])):
                    if _curr_row[2][_curr_column_idx] != _curr_row[3][_curr_column_idx]:
                        _curr_change_item_idx = self.gr_preview.insert(
                            parent=_curr_item_idx,
                            index="end",
                            iid="",
                            text=str(self.setup.destination.field_names[_curr_column_idx]),
                        )
                        self.gr_preview.item(
                            _curr_change_item_idx,
                            values=[str(_curr_row[3][_curr_column_idx]) + "=>" + str(_curr_row[2][_curr_column_idx])],
                        )
                        _changes.append(str(self.setup.destination.field_names[_curr_column_idx]))
                _curr_value = ",".join([str(_item) for _item in _changes])
                self.gr_preview.item(_curr_item_idx, values=[_curr_value])

        # Add log
        self.gr_preview.insert(parent="", index="end", iid="obpm_log", text="Log")
        if _log is not None:
            for _curr_row in _log:
                _log_fields = _curr_row.split(";")
                _curr_item_idx = self.gr_preview.insert(parent="obpm_log", index="end", iid="", text=_log_fields[0])
                _curr_value = ",".join([unquote(str(_item)) for _item in _log_fields[1:]])
                self.gr_preview.item(_curr_item_idx, values=[_curr_value])

        # Add data table

        self.gr_preview.insert(parent="", index="end", iid="obpm_data_table", text="Result")
        if _data_table is not None:
            for _curr_row in _data_table:
                _curr_item_idx = self.gr_preview.insert(
                    parent="obpm_data_table", index="end", iid="", text=_curr_row[_key_field]
                )
                _curr_value = ",".join([str(_item) for _item in _curr_row])
                self.gr_preview.item(_curr_item_idx, values=[_curr_value])
                for _curr_column_idx in range(len(_curr_row)):
                    _curr_change_item_idx = self.gr_preview.insert(
                        parent=_curr_item_idx,
                        index="end",
                        iid="",
                        text=str(self.setup.destination.field_names[_curr_column_idx]),
                    )
                    self.gr_preview.item(_curr_change_item_idx, values=[str(_curr_row[_curr_column_idx])])
        if _commit == True:
            _simulation_expression = "Merge"
        else:
            _simulation_expression = "Simulated merge"

        if not (self.setup.insert or self.setup.delete or self.setup.update):
            self.notify_task(
                _simulation_expression
                + " done. (Expecting merge results? Neither insert, delete or update is selected)",
                100,
            )
        else:
            self.notify_task(_simulation_expression + " done.", 100)

    def on_preview_selected(self, *args):
        _selection = self.gr_preview.selection()
        if len(_selection) > 0:
            _item = self.gr_preview.item(_selection[0])
            self.preview_detail.set(str(",".join([str(_item) for _item in _item["values"]])))
Exemplo n.º 2
0
    def init_GUI(self):
        """Init main application GUI"""
        print("Initializing GUI...", end="")

        self.parent.title("Optimal Framework setup")
        self.interior.notify_task = self.notify_task
        self.interior.notify_messagebox = self.notify_messagebox

        self.fr_top = BaseFrame(self.interior)
        self.fr_top.pack(side=TOP, fill=BOTH, expand=1)

        self.fr_top_left = BaseFrame(self.fr_top)
        self.fr_top_left.pack(side=LEFT, fill=BOTH, expand=1)

        self.fr_rw = BaseFrame(self.fr_top_left)
        self.fr_rw.pack(side=TOP, fill=X)

        self.btn_load_json_json = ttk.Button(self.fr_rw, text="Load", command=self.on_load_json)
        self.btn_load_json_json.pack(side=LEFT)
        self.btn_save_json = ttk.Button(self.fr_rw, text="Save", command=self.on_save_json)
        self.btn_save_json.pack(side=LEFT)
        self.btn_load_folder = ttk.Button(self.fr_rw, text="Existing", command=self.on_select_installation)
        self.btn_load_folder.pack(side=LEFT)
        self.fr_setup_filename = BaseFrame(self.fr_rw)
        self.l_setup_filename = ttk.Label(self.fr_setup_filename, text="Setup file:")
        self.l_setup_filename.pack(side=LEFT)
        self.setup_filename.set("")
        self.e_config_filename = ttk.Entry(self.fr_setup_filename, textvariable=self.setup_filename)
        self.e_config_filename.pack(side=RIGHT)

        self.fr_setup_filename.pack(side=RIGHT)

        # Settings

        self.fr_settings = BaseFrame(self.fr_top_left)
        self.fr_settings.pack(side=TOP, fill=BOTH)
        self.fr_settings.columnconfigure(2, weight=5)
        self.install_location, self.l_install_location, self.e_install_location, self.b_install_location = make_entry(
            self.fr_settings, "Install location:", 0, _button_caption=".."
        )
        self.b_install_location.config(command=self.on_select_install_folder)
        self.plugins_location, self.l_plugin_location, self.e_plugin_location, self.b_plugin_location = make_entry(
            self.fr_settings, "Plugin location:", 1, _button_caption=".."
        )
        self.b_plugin_location.config(command=self.on_select_plugin_folder)
        self.install_repository_url, self.l_install_repository_url, self.e_install_repository_url = make_entry(
            self.fr_settings, "install location:", 2
        )

        # Plugins

        self.fr_plugins_header = BaseFrame(self.fr_top_left)
        self.fr_plugins_header.pack(side=TOP)

        self.l_plugins = ttk.Label(self.fr_plugins_header, text="Plugins:")
        self.l_plugins.pack(side=TOP)

        self.fr_plugins_header_nav = BaseFrame(self.fr_plugins_header)
        self.fr_plugins_header_nav.pack(side=BOTTOM)

        self.btn_reload = Button(self.fr_plugins_header_nav, text="Refresh", command=self.on_refresh_plugins)
        self.btn_reload.pack(side=LEFT)

        self.g_plugins = FrameList(self.fr_top_left, _detail_key_text="Plugins >>", bd=1, relief=SUNKEN)
        self.g_plugins.pack(side=TOP, fill=X)
        # self.g_plugins.on_delete = self.plugins_do_on_delete
        # self.g_plugins.on_detail = self.plugins_do_on_detail

        self.btn_append_mapping = Button(self.fr_top_left, text="Append mapping", command=self.on_append_mapping)
        self.btn_append_mapping.pack(side=TOP)

        # Plugin details
        self.fr_top_right = BaseFrame(self.fr_top)
        self.fr_top_right.pack(side=RIGHT, fill=Y)

        self.l_plugin = ttk.Label(self.fr_top_right, text="Plugin details")
        self.l_plugin.pack(side=TOP)
        self.fr_plugin = BaseFrame(self.fr_top_right, bd=1, relief=SUNKEN)
        self.fr_plugin.pack(side=TOP, fill=BOTH, expand=1)
        self.plugin_url = make_entry(self.fr_plugin, "Repository URL(http):", 0)

        # Merge preview
        self.fr_Preview = ttk.Frame(self.fr_top_left)
        self.fr_Preview.pack(side=TOP, fill=BOTH, expand=1)

        self.fr_merge_actions = ttk.Frame(self.fr_Preview)
        self.fr_merge_actions.pack(side=TOP, fill=X)

        self.btn_execute_preview = Button(self.fr_merge_actions, text="Preview merge", command=self.on_preview_merge)
        self.btn_execute_preview.pack(side=LEFT)
        self.btn_execute_preview = Button(self.fr_merge_actions, text="Commit merge", command=self.on_commit_merge)
        self.btn_execute_preview.pack(side=LEFT)

        # Update
        self.merge_update = BooleanVar()
        self.e_merge_update = ttk.Checkbutton(self.fr_merge_actions, variable=self.merge_update)
        self.e_merge_update.pack(side=RIGHT)
        self.l_merge_update = ttk.Label(self.fr_merge_actions, text="Update: ")
        self.l_merge_update.pack(side=RIGHT)

        # Insert

        self.merge_insert = BooleanVar()
        self.e_merge_insert = ttk.Checkbutton(self.fr_merge_actions, variable=self.merge_insert)
        self.e_merge_insert.pack(side=RIGHT)
        self.l_merge_insert = ttk.Label(self.fr_merge_actions, text="Insert: ")
        self.l_merge_insert.pack(side=RIGHT)

        # Delete
        self.merge_delete = BooleanVar()
        self.e_merge_delete = ttk.Checkbutton(self.fr_merge_actions, variable=self.merge_delete)
        self.e_merge_delete.pack(side=RIGHT)
        self.l_merge_delete = ttk.Label(self.fr_merge_actions, text="Delete: ")
        self.l_merge_delete.pack(side=RIGHT)

        # Set post-merge SQL
        self.post_execute_sql = StringVar()
        self.btn_Post_Merge_SQL = ttk.Button(
            self.fr_merge_actions, text="Set post-merge SQL", command=self.on_post_merge_sql
        )
        self.btn_Post_Merge_SQL.pack(side=RIGHT, padx=30)

        # Preview
        self.gr_preview = ttk.Treeview(self.fr_Preview, columns=("size", "modified"))
        self.gr_preview.pack(side=TOP, fill=BOTH, expand=1)
        self.gr_preview.bind("<<TreeviewSelect>>", self.on_preview_selected)
        self.preview_detail = StringVar()
        self.e_previev_detail = ttk.Entry(self.fr_Preview, textvariable=self.preview_detail)
        self.e_previev_detail.pack(side=BOTTOM, fill=X, expand=0)

        self.fr_bottom = BaseFrame(self.interior)
        self.fr_bottom.pack(side=BOTTOM, fill=X)

        self.fr_Status_Bar = Status_Bar(self.fr_bottom)
        self.fr_Status_Bar.pack(fill=X)

        print("done.")