Browse Source

Ignores functionality enhanced

Akash Singh 3 years ago
parent
commit
4acc8bad45
4 changed files with 80 additions and 20 deletions
  1. 38 8
      CloneXls/ChangeLog.py
  2. 6 3
      CloneXls/__init__.py
  3. 21 1
      README.md
  4. 15 8
      tests/test_CloneXls.py

+ 38 - 8
CloneXls/ChangeLog.py

@@ -24,6 +24,7 @@ class ChangeLog:
         self.ignore_sheets.append(self.log_sheet_name)
         self.case_senstive_ignore = case_sensitive_ignore
         self.update_ignore_list()
+        self.headers_column = {}
 
     def update_ignore_list(self):
         # Making data inside igonre lists in lower only if case_sensitive_ignore is false, so that it will be later
@@ -48,9 +49,10 @@ class ChangeLog:
         self.check_log_sheet(clone_sheets, clone)
         sheets = self.get_sheets(source_sheets, clone_sheets)
         logs = self.get_change_logs(sheets, source, clone)
-        self.update_change_logs(logs, clone)
-        self.update_clone_file(source, clone, source_sheets)
-        clone.save(self.clone_file)
+        if logs:  # if their is no update in the non ignored data than file won't be updated
+            self.update_change_logs(logs, clone)
+            self.update_clone_file(source, clone, source_sheets)
+            clone.save(self.clone_file)
         clone.close()
         source.close()
 
@@ -127,11 +129,12 @@ class ChangeLog:
         # for change log. If the sheet already present in clone file then we delete it and create a new one with exact
         # data from source
         for sheet in source_sheets:
-            try:  # checking if the sheet exists in clone file, if it does than we first take its index than delete it
+            if sheet in clone.sheetnames:  # checking if the sheet exists in clone file, if it does than we first take its index than delete it
                 clone_sht = clone.get_sheet_by_name(sheet)
                 ind = clone.index(clone_sht)
+                ignored_headers_data = self.get_ignored_data_from_clone(clone, sheet)  # ignored data from clone file
                 clone.remove(clone_sht)
-            except:  # if clone file doesn't has that sheet than make the index containing variable to "False"
+            else:  # if clone file doesn't has that sheet than make the index containing variable to "False"
                 ind = "False"  # A string is taken instead of any empty value because the original index can contain 0
                                # which can cause conflict
             if ind != "False":  # if we have an index value than make a new sheet in that position
@@ -140,11 +143,20 @@ class ChangeLog:
                 clone.create_sheet(sheet, len(clone.sheetnames)-2)
             source_sht = source.get_sheet_by_name(sheet)
             clone_sht = clone.get_sheet_by_name(sheet)
+            skipped = 0
             for row in range(1, source_sht.max_row+1):  # looping through every row from source
                 for column in range(1, source_sht.max_column+1):  # looping through every column from source
-                    clone_sht.cell(row, column).value = source_sht.cell(row, column).value  #writing everything in clone
-                    if source_sht.cell(row, column).has_style:  # if source cell has style than copy it to clone
-                        clone_sht.cell(row, column)._style = copy(source_sht.cell(row, column)._style)
+                    if sheet in self.headers_column:
+                        if column-1 in self.headers_column[sheet]:
+                            if row == 1:  # if first row then write header
+                                clone_sht.cell(row, column).value = self.headers_column[sheet][column-1]
+                            elif row > 1:  # write data from the ignore data list, column-1 coz the header was index from 0 starting point
+                                clone_sht.cell(row, column).value = ignored_headers_data[
+                                    self.headers_column[sheet][column-1]][row-2]  # row-2 because here it starts from 2 and list starts from 0
+                            continue
+                    clone_sht.cell(row, column).value = source_sht.cell(row, column-skipped).value  #writing everything in clone
+                    if source_sht.cell(row, column-skipped).has_style:  # if source cell has style than copy it to clone
+                        clone_sht.cell(row, column-skipped)._style = copy(source_sht.cell(row, column-skipped)._style)
             for idx, rd in source_sht.row_dimensions.items():  # copying width and height of rows and columns
                 clone_sht.row_dimensions[idx] = copy(rd)
 
@@ -183,6 +195,7 @@ class ChangeLog:
         # columns. Logic is same like get_sheets method
         source_headers = [cell.value for cell in source_sht[1]]
         clone_headers = [cell.value for cell in clone_sht[1]]
+        self.headers_column[source_sht.title] = {}
         headers = []
         temp_headers = []
         new_headers = []
@@ -192,8 +205,12 @@ class ChangeLog:
                 if self.case_senstive_ignore:
                     if header not in self.ignore_headers:
                         temp_headers.append(header)
+                    else:
+                        self.headers_column[source_sht.title][source_headers.index(header)] = header
                 elif header.lower() not in self.ignore_headers:
                     temp_headers.append(header)
+                else:
+                    self.headers_column[source_sht.title][source_headers.index(header)] = header
             else:
                 temp_headers.append(header)
 
@@ -215,3 +232,16 @@ class ChangeLog:
                 if header not in source_headers:
                     removed_headers.append(header)
         return headers, new_headers, removed_headers
+
+    def get_ignored_data_from_clone(self, clone, sheet):
+        data = {}
+        sht = clone.get_sheet_by_name(sheet)
+        headers = [cell.value for cell in sht[1]]
+        if not sheet in self.headers_column:
+            return data
+        for header in self.headers_column[sheet]:
+            hd = self.headers_column[sheet][header]  # getting header from class list storing index of ignored headers
+            data[hd] = []
+            for i in range(2, sht.max_row+1):
+                data[hd].append(sht.cell(i, headers.index(hd)+1).value)
+        return data

+ 6 - 3
CloneXls/__init__.py

@@ -18,7 +18,8 @@ class CloneXls:
         if not os.path.exists(self.source_file):
             raise BaseException(f"{self.source_file} doesn't exists, please verify the path entered!")
 
-    def update_or_make_clone(self):
+    def update_or_make_clone(self, log_sheet_name="Change Logs", ignore_headers=[], ignore_sheets=[],
+            case_sensitive_ignore=False, clone=True):
         """
         This method is used to update the clone file with the change log and if their is no clone file then make one
         :return:
@@ -30,8 +31,10 @@ class CloneXls:
                 data = file.read()
             with open(self.clone_file, "wb") as file:
                 file.write(data)
-        else:
-            changeLog = ChangeLog(self.source_file, self.clone_file)
+        elif clone:
+            changeLog = ChangeLog(self.source_file, self.clone_file,
+                                  log_sheet_name=log_sheet_name, ignore_headers=ignore_headers,
+                                  ignore_sheets=ignore_sheets, case_sensitive_ignore=case_sensitive_ignore)
             changeLog.xlsxChangeLog()
         # Returning filename with absolute path of clone file
         return self.clone_file

+ 21 - 1
README.md

@@ -1,3 +1,23 @@
 # baangt-CloneXLS
 
-Clone a base XLS before it is used. Update any changes and write change log into cloned version
+This package was mainly made to use in [baangt](https://www.baangt.org/). 
+
+It's functionality is to Clone a ``excel file(xls/xlsx)`` and a new sheet is made inside cloned file which is used to 
+store the change logs. 
+
+This package contains two main classes. First is ``CloneXls`` & second is ``ChangeLog``.
+
+CloneXls
+========
+``update_or_make_clone`` is the main method of this class. On the first run it will create a clone file and if the file 
+already exist it will call ``ChangeLog`` class.
+
+ChangeLog
+=========
+This class is used to check the changes between source and cloned file and update those changes inside cloned file.
+Their are few helpful functionalities like ignore_headers & ignore_sheets these both parameters takes a list.
+Headers and sheets present in this lists will be ignored for change log and will not be updated from source.
+``xlsxChangeLog`` is the main method of this class which will trigger all the other methods like checking for changes,
+updating change log sheet, updating whole clone file from source(except ignored data). AIt will write all the 
+changes in ``Change Logs`` worksheet. This data will contain 
+``Date & time, "Sheet Name", "Header Name", "Row Number", "Old Value", "New Value"``.

+ 15 - 8
tests/test_CloneXls.py

@@ -24,10 +24,10 @@ with open(Path(input_directory).joinpath(original_file), "rb") as file:
     data = file.read()
 
 with open(Path(input_directory).joinpath(input_file), "wb") as file:
-    file.write(data)
+    file.write(data)  # creating a duplicate file as we will update the source file in the run for test to update change log
 
 
-def update_source(path):
+def update_source(path):  # update the file which is used to check the changelog
     wb = load_workbook(path)
     sht = wb.get_sheet_by_name(sheet)
     for row in range(1, sht.max_row+1):
@@ -39,29 +39,32 @@ def update_source(path):
     wb.save(path)
 
 
-def check_source(path, ignore=False):
+def check_source(path, case_sensitive=False):
+    # checks the change log sheet that if their is value added and verifies it by row number in which we have changed data
     wb = load_workbook(path)
     sht = wb.get_sheet_by_name("Change Logs")
-    if not ignore:
+    if case_sensitive:  # if case_sensitive test is done these 3 changes will the only ones
         assert int(sht.cell(2, 4).value) == 1
         assert int(sht.cell(3, 4).value) == 1
         assert int(sht.cell(4, 4).value) == 8
         assert sht.max_row == 4
-    else:
+    else:  # if case_sensitive test is done these 2 changes will the only ones
         assert int(sht.cell(2, 4).value) == 1
         assert int(sht.cell(3, 4).value) == 8
         assert sht.max_row == 3
 
 
 def test_check_source_file():
+    # Give a fake file path so it must throw a BaseException
     try:
-        CloneXls.CloneXls("tests/TestInput/CompleteBaangtWebdemo1.xlsx")
+        CloneXls.CloneXls("tests/TestInput/CompleteBaangtWebdemo_Fake.xlsx")
         assert 1 == 0
     except BaseException:
         assert 1 == 1
 
 
 def test_update_or_make_clone():
+    # Checking update_or_make_clone method of CloneXls class, that will it make a clone file if their is none
     output = str(Path(input_directory).joinpath(output_file))
     input_ = str(Path(input_directory).joinpath(input_file))
     if os.path.exists(output):
@@ -72,6 +75,7 @@ def test_update_or_make_clone():
 
 
 def test_update_or_make_clone_prefix():
+    # testing the change prefix parameter
     output = str(Path(input_directory).joinpath(output_file_2))
     input = str(Path(input_directory).joinpath(input_file))
     if os.path.exists(output):
@@ -83,6 +87,7 @@ def test_update_or_make_clone_prefix():
 
 
 def test_ChangeLog_class():
+    # creating a ChangeLog class object and verifies that if it is build successfully
     output = str(Path(input_directory).joinpath(output_file))
     input_ = str(Path(input_directory).joinpath(input_file))
     changeLog = ChangeLog(input_, output)
@@ -90,6 +95,7 @@ def test_ChangeLog_class():
 
 
 def test_ChangeLog_not_case_sensitive():
+    # Testing ChangeLog class with ignore lists treated as non case sensitive
     output = str(Path(input_directory).joinpath(output_file))
     input_ = str(Path(input_directory).joinpath(input_file))
     changeLog = ChangeLog(
@@ -99,10 +105,11 @@ def test_ChangeLog_not_case_sensitive():
            changeLog.ignore_sheets[0].islower()
     update_source(input_)
     changeLog.xlsxChangeLog()
-    check_source(output, ignore=True)
+    check_source(output)
 
 
 def test_ChangeLog_case_sensitive():
+    # Testing ChangeLog class with ignore lists treated as case sensitive
     output = str(Path(input_directory).joinpath(output_file))
     input_ = str(Path(input_directory).joinpath(input_file))
     os.remove(output)
@@ -116,7 +123,7 @@ def test_ChangeLog_case_sensitive():
     update_source(input_)
     changeLog.xlsxChangeLog()
     changeLog.xlsxChangeLog()
-    check_source(output)
+    check_source(output, case_sensitive=True)
     os.remove(input_)
     os.remove(output)