From ac32995e013933a00dd62ae16b507d80af5ae555 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Tue, 27 Aug 2024 17:08:40 +0200 Subject: [PATCH] AddFile: Add deleted flags. --- src/Model/AdditionalFile/AddFile.py | 24 +- src/Model/AdditionalFile/AddFileList.py | 2 +- src/Model/Tools/PamhyrDB.py | 48 ++++ src/Model/Tools/PamhyrListExt.py | 324 ++++++++++++++++++++++++ src/View/AdditionalFiles/UndoCommand.py | 6 +- 5 files changed, 394 insertions(+), 10 deletions(-) create mode 100644 src/Model/Tools/PamhyrListExt.py diff --git a/src/Model/AdditionalFile/AddFile.py b/src/Model/AdditionalFile/AddFile.py index 65e0e701..d3b027ea 100644 --- a/src/Model/AdditionalFile/AddFile.py +++ b/src/Model/AdditionalFile/AddFile.py @@ -112,6 +112,7 @@ class AddFile(SQLSubModel): CREATE TABLE additional_files{ext} ( {cls.create_db_add_pamhyr_id()}, enabled BOOLEAN NOT NULL, + deleted BOOLEAN NOT NULL DEFAULT FALSE, name TEXT NOT NULL, path TEXT NOT NULL, text TEXT NOT NULL, @@ -126,9 +127,9 @@ class AddFile(SQLSubModel): @classmethod def _db_update(cls, execute, version, data=None): major, minor, release = version.strip().split(".") - if major == minor == "0": - release = int(release) + release = int(release) + if major == minor == "0": if release < 8: cls._db_create(execute) return True @@ -136,6 +137,13 @@ class AddFile(SQLSubModel): if 8 < release < 13: cls._db_update_to_0_0_13(execute, data) + if major == "0" and minor == "1": + if release < 2: + execute( + "ALTER TABLE additional_files " + + "ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE" + ) + return True @classmethod @@ -166,7 +174,7 @@ class AddFile(SQLSubModel): return new table = execute( - "SELECT pamhyr_id, enabled, name, path, text, scenario " + + "SELECT pamhyr_id, enabled, deleted, name, path, text, scenario " + "FROM additional_files " + f"WHERE scenario = {scenario.id} " + f"AND pamhyr_id NOT IN ({', '.join(map(str, loaded))})" @@ -177,6 +185,7 @@ class AddFile(SQLSubModel): id = next(it) enabled = (next(it) == 1) + deleted = (next(it) == 1) name = next(it) path = next(it) text = next(it) @@ -186,6 +195,8 @@ class AddFile(SQLSubModel): id=id, enabled=enabled, name=name, path=path, text=text, status=data['status'], owner_scenario=owner_scenario ) + if deleted: + f.set_as_deleted() loaded.add(id) new.append(f) @@ -200,18 +211,17 @@ class AddFile(SQLSubModel): if not self.must_be_saved(): return True - sql = ( + execute( "INSERT INTO " + - "additional_files(pamhyr_id, enabled, " + + "additional_files(pamhyr_id, enabled, deleted, " + "name, path, text, scenario) " + "VALUES (" + - f"{self._pamhyr_id}, {self._enabled}, " + + f"{self._pamhyr_id}, {self._enabled}, {self.is_deleted()}, " + f"'{self._db_format(self._name)}', " + f"'{self._db_format(self._path)}', " + f"'{self._db_format(self._text)}', " + f"{self._status.scenario_id}" ")" ) - execute(sql) return True diff --git a/src/Model/AdditionalFile/AddFileList.py b/src/Model/AdditionalFile/AddFileList.py index f45d9cc1..1e482883 100644 --- a/src/Model/AdditionalFile/AddFileList.py +++ b/src/Model/AdditionalFile/AddFileList.py @@ -19,7 +19,7 @@ from tools import trace, timer from Model.Except import NotImplementedMethodeError -from Model.Tools.PamhyrList import PamhyrModelList +from Model.Tools.PamhyrListExt import PamhyrModelList from Model.AdditionalFile.AddFile import AddFile diff --git a/src/Model/Tools/PamhyrDB.py b/src/Model/Tools/PamhyrDB.py index 1a46b772..c2ae9a68 100644 --- a/src/Model/Tools/PamhyrDB.py +++ b/src/Model/Tools/PamhyrDB.py @@ -162,6 +162,17 @@ class SQLSubModel(PamhyrID): super(SQLSubModel, self).__init__(id) self._status = status + + # Deletion status of the model object. This status MUST be set + # to True if an object that exists in a parent scenario is + # deleted in current scenario. + self._deleted = False + + # The 'owner_scenario' is the id of the scenario to which the + # object belongs. This id CAN be different to current + # scenario, but in case of object modification, this id MUST + # be set to current scenario id. (This action is made in + # 'modified' method.) self._owner_scenario = 0 if owner_scenario == -1: if status is not None: @@ -170,12 +181,49 @@ class SQLSubModel(PamhyrID): self._owner_scenario = owner_scenario def must_be_saved(self): + """Return True if this object MUST be save in the save file. + + Returns: + True if this object MUST be save, otherelse False + """ return self._owner_scenario == self._status.scenario_id def modified(self): + """Set study status to modified and update the object + owner_scenario to current scenario + + Returns: + Nothing + """ self._owner_scenario = self._status.scenario_id self._status.modified() + def is_deleted(self): + """This object is deleted? + + Returns: + True if this object is deleted, otherelse False + """ + return self._deleted + + def set_as_deleted(self): + """Set object deleted flag to True. + + Returns: + Nothing + """ + self._deleted = True + self.modified() + + def set_as_not_deleted(self): + """Set object deleted flag to False. + + Returns: + Nothing + """ + self._deleted = False + self.modified() + def _db_format(self, value): # Replace ''' by ''' to preserve SQL injection if type(value) is str: diff --git a/src/Model/Tools/PamhyrListExt.py b/src/Model/Tools/PamhyrListExt.py new file mode 100644 index 00000000..ecf42459 --- /dev/null +++ b/src/Model/Tools/PamhyrListExt.py @@ -0,0 +1,324 @@ +# PamhyrList.py -- Pamhyr Abstract List object for the Model +# Copyright (C) 2023-2024 INRAE +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# -*- coding: utf-8 -*- + +import logging + +from copy import copy +from tools import trace, timer + +from Model.Tools.PamhyrDB import SQLSubModel +from Model.Except import NotImplementedMethodeError + +logger = logging.getLogger() + + +class PamhyrModelList(SQLSubModel): + _sub_classes = [] + + def __init__(self, status=None): + super(PamhyrModelList, self).__init__() + + self._status = status + self._lst = [] + + ####### + # SQL # + ####### + + @classmethod + def _db_create(cls, execute): + return cls._create_submodel(execute) + + @classmethod + def _db_update(cls, execute, version, data=None): + return cls._update_submodel(execute, version, data) + + @classmethod + def _db_load(cls, execute, data=None): + raise NotImplementedMethodeError(cls, cls._db_load) + + def _db_save(self, execute, data=None): + raise NotImplementedMethodeError(self, self._db_save) + + ################ + # MODEL METHOD # + ################ + + @property + def lst(self): + """Return the PamhyrList as a Python list + + Return the PamhyrList as a Python list. This list is the + current list witout deleted object used in the PamhyrList + object. **If you need to modify it, please make a copy.** + + Returns: The Python list + """ + return list( + filter( + lambda el: not el.is_deleted(), + self._lst + ) + ) + + def __len__(self): + return len(self.lst) + + def index(self, el): + return self.lst.index(el) + + def get(self, index): + return self.lst[index] + + def set(self, index, new): + self.lst[index] = new + if self._status is not None: + self._status.modified() + + def new(self, index): + """Create new elements and add it to list + + Args: + index: The index of new elements + + Returns: + The new elements + """ + raise NotImplementedMethodeError(self, self.new) + + def insert(self, index, new): + self._lst.insert(index, new) + + if self._status is not None: + self._status.modified() + + def undelete(self, lst): + """Delete a list of elements + + Args: + lst: The list of elements + + Returns: + Nothing + """ + for el in lst: + el.set_as_not_deleted() + + def delete(self, lst): + """Delete a list of elements + + Args: + lst: The list of elements + + Returns: + Nothing + """ + for el in lst: + el.set_as_deleted() + + def delete_i(self, indexes): + """Delete elements from list of indexes + + Args: + indexes: The elements indexes + + Returns: + Nothing + """ + lst = list( + map( + lambda x: x[1], + filter( + lambda x: x[0] in indexes, + enumerate(self.lst) + ) + ) + ) + self.delete(lst) + + def sort(self, reverse=False, key=None): + self._lst.sort(reverse=reverse, key=key) + + if self._status is not None: + self._status.modified() + + def move_up(self, index): + if index < len(self._lst): + next = index - 1 + + lst = self._lst + lst[index], lst[next] = lst[next], lst[index] + + if self._status is not None: + self._status.modified() + + def move_down(self, index): + if index >= 0: + prev = index + 1 + + lst = self._lst + lst[index], lst[prev] = lst[prev], lst[index] + + if self._status is not None: + self._status.modified() + + +class PamhyrModelListWithTab(SQLSubModel): + _tabs_list = [] + _sub_classes = [] + + def __init__(self, status=None): + super(PamhyrModelListWithTab, self).__init__() + + self._status = status + self._tabs = {} + for tab in self._tabs_list: + self._tabs[tab] = [] + + ####### + # SQL # + ####### + + @classmethod + def _db_create(cls, execute): + return cls._create_submodel(execute) + + @classmethod + def _db_update(cls, execute, version, data=None): + return cls._update_submodel(execute, version, data) + + @classmethod + def _db_load(cls, execute, data=None): + raise NotImplementedMethodeError(cls, cls._db_load) + + def _db_save(self, execute, data=None): + raise NotImplementedMethodeError(self, self._db_save) + + ################ + # MODEL METHOD # + ################ + + def len(self, lst): + """Size of tab list + + Args: + lst: The tab name + + Returns: + The size of the tab list + """ + return len(self._tabs[lst]) + + def get_tab(self, lst): + """Get tab list (copy) from name + + Args: + lst: The tab name + + Returns: + Nothing + """ + return self._tabs[lst].copy() + + def get(self, lst, index): + return self._tabs[lst][index] + + def set(self, lst, index, new): + self._tabs[lst][index] = new + self._status.modified() + + def new(self, lst, index): + """Create new elements and add it to list + + Args: + lst: The tab name + index: The index of new elements + + Returns: + The new elements + """ + raise NotImplementedMethodeError(self, self.new) + + def insert(self, lst, index, new): + """Insert element in tab + + Args: + lst: The tab name + index: The index of new element + new: The new elements + + Returns: + Nothing + """ + self._tabs[lst].insert(index, new) + self._status.modified() + + def delete(self, lst, els): + """Delete elements from specific tab + + Args: + lst: The tab name + els: The elements list + + Returns: + Nothing + """ + for el in els: + self._tabs[lst].remove(el) + self._status.modified() + + def delete_i(self, lst, indexes): + """Delete elements from specific tab + + Args: + lst: The tab name + indexes: The elements indexes + + Returns: + Nothing + """ + els = list( + map( + lambda x: x[1], + filter( + lambda x: x[0] in indexes, + enumerate(self._tabs[lst]) + ) + ) + ) + self.delete(lst, els) + + def sort(self, lst, reverse=False, key=None): + self._tabs[lst].sort(reverse=reverse, key=key) + self._status.modified() + + def move_up(self, lst, index): + if index < len(self._tabs[lst]): + next = index - 1 + + lst = self._tabs[lst] + lst[index], lst[next] = lst[next], lst[index] + self._status.modified() + + def move_down(self, lst, index): + if index >= 0: + prev = index + 1 + + lst = self._tabs[lst] + lst[index], lst[prev] = lst[prev], lst[index] + self._status.modified() diff --git a/src/View/AdditionalFiles/UndoCommand.py b/src/View/AdditionalFiles/UndoCommand.py index 631c91fa..0fd5ef19 100644 --- a/src/View/AdditionalFiles/UndoCommand.py +++ b/src/View/AdditionalFiles/UndoCommand.py @@ -64,7 +64,8 @@ class AddCommand(QUndoCommand): if self._new is None: self._new = self._files.new(self._row) else: - self._files.insert(self._row, self._new) + self._files.undelete([self._new]) + # self._files.insert(self._row, self._new) class DelCommand(QUndoCommand): @@ -76,7 +77,8 @@ class DelCommand(QUndoCommand): self._old = self._files.get(row) def undo(self): - self._files.insert(self._row, self._old) + self._files.undelete([self._old]) + # self._files.insert(self._row, self._old) def redo(self): self._files.delete_i([self._row])