Browse Source

update from XLSX

aguryev 4 years ago
parent
commit
c87baa79c0

+ 5 - 0
flask/app/static/css/styles.css

@@ -40,6 +40,11 @@ body {
   width: 10rem;
 }
 
+/* dialog */
+.dialog {
+  justify-content: center !important;
+}
+
 
 /* widths */
 .w-20 {

+ 28 - 17
flask/app/static/js/testrun.js

@@ -52,16 +52,29 @@
     }
 
     // delete item
-    function delete_item(item_type, item_name, item_id) {
-        if (confirm(`You are about to delete '${item_name}'`)) {
-            const request = new XMLHttpRequest();
-            request.open('POST', `/${item_type}/${item_id}/delete`);
-            request.onload = () => {
-                const response = request.responseText;
-                document.write(response);
-            }
-            request.send();
-        };
+    function set_delete_item(type_name, type, item_id) {
+        /* set data in delete modal */
+        document.querySelector('#deleteLabel').innerHTML = `Delete ${type_name} ID #${item_id}`;
+        const btns = document.querySelector('#deleteButtons');
+        btns.setAttribute('data-type', type);
+        btns.setAttribute('data-id', item_id);
+    }
+
+    function delete_item(e, cascade) {
+        /* delete item */
+        const request = new XMLHttpRequest();
+        if (cascade) {
+            // cascade delete: POST request
+            request.open('POST', `/${e.dataset['type']}/${e.dataset['id']}/delete`);
+        } else {
+            // single item delete: DELETE request
+            request.open('DELETE', `/${e.dataset['type']}/${e.dataset['id']}/delete`);
+        }
+        
+        request.onload = () => {
+            window.location.reload(true); 
+        }
+        request.send();
     }
 
     // select multiple with chips
@@ -91,13 +104,6 @@
     function delete_chip(e, name) {
         var list = document.getElementById(`${name}Opt`);
         list.children[e.dataset['id']].disabled = false;
-        /*
-        for (var i = 1; i < list.childElementCount; i++) {
-            if (list.children[i].text == e.dataset['id']) {
-                list.children[i].disabled = false;
-            }
-        }
-        */
         e.parentElement.removeChild(e);
     }
 
@@ -176,4 +182,9 @@
             document.querySelector('#exportButton').style.display = 'none';
         };
         request.send();
+    }
+
+    function set_update_item(item_id) {
+        document.querySelector('#updateLabel').innerHTML = `Update Testrun ID #${item_id}`;
+        document.querySelector('#updateForm').setAttribute('action', `/testrun/${item_id}/import`);
     }

+ 62 - 4
flask/app/templates/testrun/item_list.html

@@ -310,14 +310,18 @@
                 <!-- buttons -->
                 <p>
                     <a class="btn btn-primary px-5 my-3 mr-3" href="/{{ type }}/{{ item.id }}/edit" role="button">Edit</a>
-                    <!-- Testrun Export button -->
+                    <!-- Testrun buttons -->
                     {% if type == 'testrun' %}
-                        <!--a class="btn btn-success px-5 my-3 mr-3" href="/testrun/xlsx/{{ item.id }}" role="button">Export</a-->
+                        <!-- Update button -->
+                        <button type="button" class="btn btn-primary px-5 my-3 mr-3" data-toggle="modal" data-target="#updateModal" onmousedown="set_update_item('{{ item.id }}')">
+                            Update
+                        </button>
+                        <!-- Export button -->
                         <button type="button" class="btn btn-success px-5 my-3 mr-3" data-toggle="modal" data-target="#exportModal" onmousedown="set_export_id('{{ item.id }}')">
                             Export
                         </button>
                     {% endif %}
-                    <button class="btn btn-danger px-5 my-3" onclick="delete_item('{{ type }}', '{{ item.name}}', '{{ item.id }}')">
+                    <button class="btn btn-danger px-5 my-3" data-toggle="modal" data-target="#deleteModal" onmousedown="set_delete_item('{{ type|name_by_type }}','{{ type }}','{{ item.id }}')">
                         Delete
                     </button>
                 </p>
@@ -326,7 +330,36 @@
         </div>
     {% endfor %}
 
-    <!--Export  Modal -->
+    <!-- Update Modal -->
+    {% if type == 'testrun' %}
+        <div class="modal fade" id="updateModal" tabindex="-1" role="dialog" aria-labelledby="updateModalLabel" aria-hidden="true">
+            <div class="modal-dialog" role="document">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <h5 class="modal-title" id="updateLabel"></h5>
+                        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                            <span aria-hidden="true">&times;</span>
+                        </button>
+                    </div>
+                    <form method="post" enctype="multipart/form-data" id="updateForm">
+                        {{ form.hidden_tag() }}
+                        <div class="modal-body">
+                            <div class="custom-file">
+                                {{ form.file(class="custom-file-input", accept=".xlsx", onchange="get_file(this)") }}
+                                <label class="custom-file-label" for="{{ form.file.name }}">Choose a file</label>
+                            </div>
+                        </div>
+                        <div class="modal-footer">
+                            <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
+                            <button type="submit" class="btn btn-primary">Update</button>
+                        </div>
+                    </form>
+                </div>
+            </div>
+        </div>
+    {% endif %}
+
+    <!-- Export  Modal -->
     <div class="modal fade" id="exportModal" data-backdrop="static" tabindex="-1" role="dialog" aria-labelledby="exportLabel" aria-hidden="true">
         <div class="modal-dialog" role="document">
             <div class="modal-content">
@@ -358,6 +391,31 @@
         </div>
     </div>
 
+    <!-- Delete Modal -->
+    <div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteLabel" aria-hidden="true">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title" id="deleteLabel"></h5>
+                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                        <span aria-hidden="true">&times;</span>
+                    </button>
+                </div>
+                <div class="modal-body">
+                    Delete all child elements?
+                </div>
+                <div class="modal-footer dialog" id="deleteButtons">
+                    <button type="button" class="btn btn-secondary btn-right" onclick="delete_item(this.parentElement, true)">
+                        Yes
+                    </button>
+                    <button type="button" class="btn btn-primary btn-right" onclick="delete_item(this.parentElement, false)">
+                        No
+                    </button>
+                </div>
+            </div>
+        </div>
+    </div>
+
 
 
 </div>

+ 340 - 142
flask/app/utils.py

@@ -2,6 +2,17 @@ import os
 from app import models, db, app
 from datetime import datetime
 import xlsxwriter, xlrd, json
+from flask_login import current_user
+
+#
+# Default VALUES
+#
+CLASSNAME_TESTCASESEQUENCE = 'GC.CLASSES_TESTCASESEQUENCE'
+CLASSNAME_TESTCASE = 'GC.CLASSES_TESTCASE'
+CLASSNAME_TESTSTEP = 'GC.CLASSES_TESTSTEPMASTER'
+
+BROWSER_TYPE = 'GC.BROWSER_FIREFOX'
+TESTCASE_TYPE = 'Browser'
 
 #
 # item categories
@@ -19,6 +30,31 @@ def getItemCategories():
 	return categories
 
 #
+# get name of the item_type
+#
+def getItemType(item_type, plural=False):	
+	# main items 
+	if item_type == 'testrun':
+		name = 'Testrun'
+	elif item_type == 'testcase_sequence':
+		name = 'Test Case Sequence'
+	elif item_type == 'testcase':
+		name = 'Test Case'
+	elif item_type == 'teststep_sequence':
+		name = 'Test Step Sequence'
+	elif item_type == 'teststep':
+		name = 'Test Step'
+	else:
+		# wrong item_type
+		return ''
+
+	# check for plurals
+	if plural:
+		name += 's'
+
+	return name
+
+#
 # generate choices of items
 #
 
@@ -109,6 +145,22 @@ def getComparisionId(option):
 # Get Items By Name
 #
 
+def getOrCreateClassNameByName(name, description):
+	# get ClassName from DB
+	classname = models.ClassName.query.filter_by(name=name).first()
+	if classname is None:
+		# create ClassName if it doesn't exist
+		classname = models.ClassName(
+			name=name,
+			description=description,
+		)
+		db.session.add(classname)
+		db.session.commit()
+		app.logger.info(f'Created ClassName ID #{classname.id} by {current_user}.')
+
+	return classname 
+
+
 def getBrowserTypeByName(name):
 	# browser mapper
 	bm = {
@@ -137,9 +189,79 @@ def getBooleanValue(value):
 	else:
 		return False
 
+#
+# Cascade Delete
+#
+
+def deleteCascade(item_type, item_id, ):
+	#
+	# implementation of cascade delete of items
+	#
+
+	# delete Testrun and its children
+	if item_type == 'testrun':
+		item = models.Testrun.query.get(item_id)
+		# delete children TestCaseSequences
+		for child in item.testcase_sequences:
+			deleteCascade('testcase_sequence', child.id)
+		# delete Testrun
+		db.session.delete(item)
+		db.session.commit()
+		app.logger.info(f'Deleted {item_type} id {item_id} by {current_user}.')
+
+	# delete TestCaseSequence and its children
+	elif item_type == 'testcase_sequence':
+		item = models.TestCaseSequence.query.get(item_id)
+		# check if item has not more then one parent
+		if len(item.testrun) <= 1:
+			# delete children TestCases
+			for child in item.testcases:
+				deleteCascade('testcase', child.id)
+			# delete TestCaseSequence
+			db.session.delete(item)
+			db.session.commit()
+			app.logger.info(f'Deleted {item_type} id {item_id} by {current_user}.')
+
+	# delete TestCase and its children
+	elif item_type == 'testcase':
+		item = models.TestCase.query.get(item_id)
+		# check if item has not more then one parent
+		if len(item.testcase_sequence) <= 1:
+			# delete children TestCaseSequences
+			for child in item.teststep_sequences:
+				deleteCascade('teststep_sequence', child.id)
+			# delete TestCase
+			db.session.delete(item)
+			db.session.commit()
+			app.logger.info(f'Deleted {item_type} id {item_id} by {current_user}.')
+
+	# delete TestCaseSequence and its children
+	elif item_type == 'teststep_sequence':
+		item = models.TestStepSequence.query.get(item_id)
+		# check if item has not more then one parent
+		if len(item.testcase) <= 1:
+			# delete children TestStepExecutions
+			for child in item.teststeps:
+				deleteCascade('teststep', child.id)
+			# delete TestCaseSequence
+			db.session.delete(item)
+			db.session.commit()
+			app.logger.info(f'Deleted {item_type} id {item_id} by {current_user}.')
+
+	# delete TestStepExecution
+	elif item_type == 'teststep':
+		item = models.TestStepExecution.query.get(item_id)
+		db.session.delete(item)
+		db.session.commit()
+		app.logger.info(f'Deleted {item_type} id {item_id} by {current_user}.')
+
+	# invalid type
+	else:
+		raise Exception(f'Item type {item_type} does not exists.')
+
 
 #
-# Testrun format convertions
+# Testrun export/import
 #
 
 def exportXLSX(testrun_id):
@@ -262,31 +384,52 @@ def exportXLSX(testrun_id):
 	return xlsx_file
 
 
-def importXLSX(user, xlsx_file):
+def importXLSX(xlsx_file, item_id=None):
 	#
 	# imports testrun from xlsx file
 	#
 
-	app.logger.info(f'Importing a Testrun from {xlsx_file.filename} by {user}.')
+	if item_id is None:
+		app.logger.info(f'Importing a Testrun from {xlsx_file.filename} by {current_user}.')
+	else:
+		# get item
+		testrun = models.Testrun.query.get(item_id)
+		if testrun is None: # item does not exist
+			raise Exception(f'Testrun ID #{item_id} does not exists.')
+		app.logger.info(f'Updating Testrun ID #{item_id} from {xlsx_file.filename} by {current_user}.')
+	
 	# open xlsx
 	try:
 		xl = xlrd.open_workbook(file_contents=xlsx_file.read())
-	except XLRDError:
+	except xlrd.XLRDError:
 		raise Exception(f'File "{xlsx_file.filename}" could not be imporeted.')
-
-	# create Testrun object
+	# get file name
 	file_name = os.path.basename(xlsx_file.filename)
-	testrun  = models.Testrun(
-		name=file_name,
-		description=f'Imported from "{file_name}"',
-		creator=user,
-	)
-	db.session.add(testrun)
-	db.session.commit()
-	app.logger.info(f'Created Testrun id {testrun.id} by {user}.')
-
-	# create TestCaseSequences
-	testcase_sequences = {}
+
+	# Testrun object
+	if item_id is None:
+		# create Testrun
+		testrun  = models.Testrun(
+			name=file_name,
+			description=f'Imported from "{file_name}".',
+			creator=current_user,
+		)
+		db.session.add(testrun)
+		db.session.commit()
+		app.logger.info(f'Created Testrun ID #{testrun.id} by {current_user}.')
+	else:
+		# update Testrun
+		testrun.description = f'Updated from "{file_name}".'
+		testrun.editor = current_user
+		testrun.edited = datetime.utcnow()
+		db.session.commit()
+		app.logger.info(f'Updated Testrun ID #{item_id} by {current_user}.')
+
+	# TestCaseSequence objects
+	if item_id is None:
+		testcase_sequences = {}
+	else:
+		testcase_sequences = {i+1: testrun.testcase_sequences[i] for i in range(len(testrun.testcase_sequences))}
 	if 'TestCaseSequence' in xl.sheet_names():
 		# get sheet
 		testcase_sequence_sheet = xl.sheet_by_name('TestCaseSequence')
@@ -300,18 +443,17 @@ def importXLSX(user, xlsx_file):
 			else:
 				# get the number from sheet
 				n = int(testcase_sequence_sheet.cell(row, headers['Number']).value)
+			
 			# ClassName
 			if headers.get('SequenceClass') is None:
 				# default ClassName name
-				name = 'GC.CLASSES_TESTCASESEQUENCE'
+				name = CLASSNAME_TESTCASESEQUENCE
 			else:
 				# get ClassName name from sheet
 				name = testcase_sequence_sheet.cell(row, headers['SequenceClass']).value
-			classname = models.ClassName(
-				name=name,
-				description=f'Imported from "{file_name}"',
-			)
-			db.session.add(classname)
+			# get ClassName from DB or create if it doesn't exist
+			classname = getOrCreateClassNameByName(name, f'Imported from "{file_name}".')
+						
 			# DataFile
 			if headers.get('TestDataFileName') is None:
 				# default DataFile name
@@ -319,59 +461,92 @@ def importXLSX(user, xlsx_file):
 			else:
 				# get DataFile name from sheet
 				name = testcase_sequence_sheet.cell(row, headers['TestDataFileName']).value
+			# get DataFile from DB
+			datafile = models.DataFile.query.filter_by(filename=name).first()
+			if datafile is None:
+				# create DataFile if it doesn't exist
+				datafile  = models.DataFile(
+					filename=name,
+					creator=current_user,
+				)
+				db.session.add(datafile)
+				db.session.commit()
+				app.logger.info(f'Created DataFile ID #{datafile.id} by {current_user}.')
+			
+			# TestCaseSequence
+			if n in testcase_sequences:
+				# update item
+				testcase_sequences[n].description = f'Updated from "{file_name}".'
+				testcase_sequences[n].editor = current_user
+				testcase_sequences[n].edited = datetime.utcnow()
+				testcase_sequences[n].classname = classname
+				testcase_sequences[n].datafiles = [datafile]
+				db.session.commit()
+				app.logger.info(f'Updated TestCaseSequence ID #{testcase_sequences[n].id} by {current_user}.')
+			else:
+				# create item
+				testcase_sequences[n] = models.TestCaseSequence(
+					name=f'{file_name}_{row}',
+					description=f'Imported from "{file_name}"',
+					creator=current_user,
+					classname=classname,
+					datafiles=[datafile],
+					testrun=[testrun],
+				)
+				db.session.add(testcase_sequences[n])
+				db.session.commit()
+				app.logger.info(f'Created TestCaseSequence ID #{testcase_sequences[n].id} by {current_user}.')
+			
+	else:
+		# create default TestCaseSequence
+		
+		# ClassName
+		# get ClassName from DB or create if it doesn't exist
+		classname = getOrCreateClassNameByName(CLASSNAME_TESTCASESEQUENCE, 'Default for TestCaseSequence.')
+		
+		# DataFile
+		# get DataFile from DB
+		datafile = models.DataFile.query.filter_by(filename=file_name).first()
+		if datafile is None:
+			# create DataFile if it doesn't exist
 			datafile  = models.DataFile(
-				filename=name,
-				creator=user,
+				filename=file_name,
+				creator=current_user,
 			)
 			db.session.add(datafile)
 			db.session.commit()
-			app.logger.info(f'Created ClassName id {classname.id} by {user}.')
-			app.logger.info(f'Created DataFile id {datafile.id} by {user}.')
-			# TestCaseSequence
-			testcase_sequences[n] = models.TestCaseSequence(
-				name=f'{file_name}_{row}',
-				description=f'Imported from "{file_name}"',
-				creator=user,
+			app.logger.info(f'Created DataFile ID #{datafile.id} by {current_user}.')
+
+		# TestCaseSequence
+		if 1 in testcase_sequences:
+			# update item
+			testcase_sequences[1].description = f'Updated to default.'
+			testcase_sequences[1].editor = current_user
+			testcase_sequences[1].edited = datetime.utcnow()
+			testcase_sequences[1].classname = classname
+			testcase_sequences[1].datafiles = [datafile]
+			db.session.commit()
+			app.logger.info(f'Updated TestCaseSequence ID #{testcase_sequences[1].id} by {current_user}.')
+		else:
+			# create item
+			testcase_sequences[1] = models.TestCaseSequence(
+				name=f'{file_name}',
+				description=f'Default for "{file_name}"',
+				creator=current_user,
 				classname=classname,
 				datafiles=[datafile],
 				testrun=[testrun],
 			)
-			db.session.add(testcase_sequences[n])
+			db.session.add(testcase_sequences[1])
 			db.session.commit()
-			app.logger.info(f'Created TestCaseSequence id {testcase_sequences[n].id} by {user}.')
-			
+			app.logger.info(f'Created TestCaseSequence ID #{testcase_sequences[1].id} by {current_user}.')
+
+	# TestCase objects
+	if item_id is None:
+		testcases = {i+1: {} for i in range(len(testcase_sequences))}
 	else:
-		# create default TestCaseSequence
-		# ClassName
-		classname = models.ClassName(
-			name='GC.CLASSES_TESTCASESEQUENCE',
-			description=f'Default for TestCaseSequence',
-		)
-		db.session.add(classname)
-		# DataFile
-		datafile  = models.DataFile(
-			filename=file_name,
-			creator=user,
-		)
-		db.session.add(datafile)
-		db.session.commit()
-		app.logger.info(f'Created ClassName id {classname.id} by {user}.')
-		app.logger.info(f'Created DataFile id {datafile.id} by {user}.')
-		# TestCaseSequence
-		testcase_sequences[1] = models.TestCaseSequence(
-			name=f'{file_name}_1',
-			description=f'Default for "{file_name}"',
-			creator=user,
-			classname=classname,
-			datafiles=[datafile],
-			testrun=[testrun],
-		)
-		db.session.add(testcase_sequences[1])
-		db.session.commit()
-		app.logger.info(f'Created TestCaseSequence id {testcase_sequences[1].id} by {user}.')
+		testcases = {index: {j+1: item.testcases[j] for j in range(len(item.testcases))} for index, item in testcase_sequences.items()}
 
-	# create TestCases
-	testcases = {}
 	if 'TestCase' in xl.sheet_names():
 		# get sheet
 		testcase_sheet = xl.sheet_by_name('TestCase')
@@ -379,26 +554,32 @@ def importXLSX(user, xlsx_file):
 		headers = {h[1]: h[0] for h in enumerate(testcase_sheet.row_values(0))}
 		# get TestCases
 		for row in range(1, testcase_sheet.nrows):
+			# get TestCaseSequenceNumber
+			if headers.get('TestCaseSequenceNumber') is None:
+				# default number is 1
+				i = 1
+			else:
+				# get the number from sheet
+				i = int(testcase_sheet.cell(row, headers['TestCaseSequenceNumber']).value)
+
+			# get TestCaseNumber
 			if headers.get('TestCaseNumber') is None:
 				# default number is 1
 				n = 1
 			else:
 				# get the number from sheet
 				n = int(testcase_sheet.cell(row, headers['TestCaseNumber']).value)
+			
 			# ClassName
 			if headers.get('TestCaseClass') is None:
 				# default ClassName name
-				name = 'GC.CLASSES_TESTCASE'
+				name = CLASSNAME_TESTCASE
 			else:
 				# get ClassName name from sheet
 				name = testcase_sheet.cell(row, headers['TestCaseClass']).value
-			classname = models.ClassName(
-				name=name,
-				description=f'Imported from "{file_name}"',
-			)
-			db.session.add(classname)
-			db.session.commit()
-			app.logger.info(f'Created ClassName id {classname.id} by {user}.')
+			# get ClassName from DB or create if it doesn't exist
+			classname = getOrCreateClassNameByName(name, f'Imported from "{file_name}".')
+
 			# TestCase
 			# Browser Type
 			if headers.get('Browser') is None:
@@ -417,49 +598,63 @@ def importXLSX(user, xlsx_file):
 				if testcase_type is None:
 					raise Exception(f'Unknown testcase type "{name}": sheet "TestCase" row {row+1}.')
 
-			# get TestCase Sequence Number
-			if headers.get('TestCaseSequenceNumber') is None:
-				# default number is 1
-				testcase_sequence_n = 1
+			if n in testcases[i]:
+				# update item
+				testcases[i][n].description = f'Updated from "{file_name}".'
+				testcases[i][n].editor = current_user
+				testcases[i][n].edited = datetime.utcnow()
+				testcases[i][n].classname = classname
+				testcases[i][n].browser_type = browser_type
+				testcases[i][n].testcase_type = testcase_type
+				db.session.commit()
+				app.logger.info(f'Updated TestCase ID #{testcases[n].id} by {current_user}.')
 			else:
-				# get the number from sheet
-				testcase_sequence_n = int(testcase_sheet.cell(row, headers['TestCaseSequenceNumber']).value)
-
-			testcases[n]  = models.TestCase(
-				name=f'{file_name}_{row}',
-				description=f'Imported from "{file_name}"',
-				creator=user,
-				classname=classname,
-				browser_type=browser_type,
-				testcase_type=testcase_type,
-				testcase_sequence=[testcase_sequences[testcase_sequence_n]],
-			)
-			db.session.add(testcases[n])
-			db.session.commit()
-			app.logger.info(f'Created TestCase id {testcases[n].id} by {user}.')
+				# create item
+				testcases[i][n]  = models.TestCase(
+					name=f'{file_name}_{row}',
+					description=f'Imported from "{file_name}".',
+					creator=current_user,
+					classname=classname,
+					browser_type=browser_type,
+					testcase_type=testcase_type,
+					testcase_sequence=[testcase_sequences[i]],
+				)
+				db.session.add(testcases[i][n])
+				db.session.commit()
+				app.logger.info(f'Created TestCase ID #{testcases[i][n].id} by {current_user}.')
 	else:
 		# create default TestCase
+
 		# ClassName
-		classname = models.ClassName(
-			name='GC.CLASSES_TESTCASE',
-			description='Default for TestCase',
-		)
-		db.session.add(classname)
-		db.session.commit()
-		app.logger.info(f'Created ClassName id {classname.id} by {user}.')
+		# get ClassName from DB or create if it doesn't exist
+		classname = getOrCreateClassNameByName(CLASSNAME_TESTCASE, 'Default for TestCase.')
+
 		# TestCase
-		testcases[1]  = models.TestCase(
-			name=f'{file_name}_1',
-			description=f'Default for "{file_name}"',
-			creator=user,
-			classname=classname,
-			browser_type=getBrowserTypeByName('GC.BROWSER_FIREFOX'),
-			testcase_type=getTestCaseTypeByName('Browser'),
-			testcase_sequence=[testcase_sequences[1]]
-		)
-		db.session.add(testcases[1])
-		db.session.commit()
-		app.logger.info(f'Created TestCase id {testcases[1].id} by {user}.')
+		if 1 in testcases[1]:
+			# update item
+			testcases[1][1].description = f'Updated to default.'
+			testcases[1][1].editor = current_user
+			testcases[1][1].edited = datetime.utcnow()
+			testcases[1][1].classname = classname
+			testcases[1][1].browser_type = getBrowserTypeByName(BROWSER_TYPE)
+			testcases[1][1].testcase_type = getTestCaseTypeByName(TESTCASE_TYPE)
+			db.session.commit()
+			app.logger.info(f'Updated TestCase ID #{testcases[1][1].id} by {current_user}.')
+		else:
+			# create item
+			testcases[1][1]  = models.TestCase(
+				name=f'{file_name}',
+				description=f'Default for "{file_name}".',
+				creator=current_user,
+				classname=classname,
+				browser_type=getBrowserTypeByName(BROWSER_TYPE),
+				testcase_type=getTestCaseTypeByName(TESTCASE_TYPE),
+				testcase_sequence=[testcase_sequences[1]],
+			)
+			db.session.add(testcases[1][1])
+			db.session.commit()
+			app.logger.info(f'Created TestCase ID #{testcases[1][1].id} by {current_user}.')
+
 
 	# create TestSteps
 	teststeps = {}
@@ -470,66 +665,69 @@ def importXLSX(user, xlsx_file):
 		headers = {h[1]: h[0] for h in enumerate(teststep_sheet.row_values(0))}
 		# get TestSteps
 		for row in range(1, teststep_sheet.nrows):
+			# get TestStepNumber
 			if headers.get('TestStepNumber') is None:
 				# default number is 1
 				n = 1
 			else:
 				# get the number from sheet
 				n = int(teststep_sheet.cell(row, headers['TestStepNumber']).value)
+
+			# get TestCaseNumber
+			if headers.get('TestCaseNumber') is None:
+				# default number is 1
+				j = 1
+			else:
+				# get the number from sheet
+				j = int(teststep_sheet.cell(row, headers['TestCaseNumber']).value)
+
+			# get TestCaseSequenceNumber
+			if headers.get('TestCaseSequenceNumber') is None:
+				# default number is 1
+				i = 1
+			else:
+				# get the number from sheet
+				i = int(teststep_sheet.cell(row, headers['TestCaseSequenceNumber']).value)
+
 			# ClassName
 			if headers.get('TestStepClass') is None:
 				# default ClassName name
-				name = 'GC.CLASSES_TESTSTEPMASTER'
+				name = CLASSNAME_TESTSTEP
 			else:
 				# get ClassName name from sheet
 				name = teststep_sheet.cell(row, headers['TestStepClass']).value
-			classname = models.ClassName(
-				name=name,
-				description=f'Imported from "{file_name}"',
-			)
-			db.session.add(classname)
-			db.session.commit()
-			app.logger.info(f'Created ClassName id {classname.id} by {user}.')
+			# get ClassName from DB or create if it doesn't exist
+			classname = getOrCreateClassNameByName(name, f'Imported from "{file_name}".')
+			
 			# TestCase
-			# get TestCase Sequence Number
-			if headers.get('TestCaseNumber') is None:
-				# default number is 1
-				testcase_n = 1
-			else:
-				# get the number from sheet
-				testcase_n = int(teststep_sheet.cell(row, headers['TestCaseNumber']).value)
-
+			# ---------------------------------------------------------------> continue with update
 			teststeps[n]  = models.TestStepSequence(
 				name=f'{file_name}_{row}',
 				description=f'Imported from "{file_name}"',
-				creator=user,
+				creator=current_user,
 				classname=classname,
-				testcase=[testcases[testcase_n]],
+				testcase=[testcases[i][j]],
 			)
 			db.session.add(teststeps[n])
 			db.session.commit()
-			app.logger.info(f'Created TestStepSequence id {teststeps[n].id} by {user}.')
+			app.logger.info(f'Created TestStepSequence id {teststeps[n].id} by {current_user}.')
 	else:
 		# create default TestStep
 		# ClassName
-		classname = models.ClassName(
-			name='GC.CLASSES_TESTSTEPMASTER',
-			description='Default for TestStep',
-		)
-		db.session.add(classname)
-		db.session.commit()
-		app.logger.info(f'Created ClassName id {classname.id} by {user}.')
+		# get ClassName from DB or create if it doesn't exist
+		classname = getOrCreateClassNameByName(CLASSNAME_TESTSTEP, 'Default for TestStep')
+		
 		# TestStep
 		teststeps[1]  = models.TestStepSequence(
 			name=f'{file_name}_1',
 			description=f'Default for "{file_name}"',
-			creator=user,
+			creator=current_user,
 			classname=classname,
-			testcase=[testcases[1]]
+			testcase=[testcases[1][1]]
 		)
 		db.session.add(teststeps[1])
 		db.session.commit()
-		app.logger.info(f'Created TestStepSequence id {teststeps[1].id} by {user}.')
+		app.logger.info(f'Created TestStepSequence id {teststeps[1].id} by {current_user}.')
 
 	# create TestStepsExecutions
 	if 'TestStepExecution' in xl.sheet_names():
@@ -606,7 +804,7 @@ def importXLSX(user, xlsx_file):
 			teststepex  = models.TestStepExecution(
 				name=f'{file_name}_{row}',
 				description=f'Imported from "{file_name}"',
-				creator=user,
+				creator=current_user,
 				teststep_sequence=teststeps[teststep_n],
 				activity_type=activity_type,
 				locator_type=locator_type,
@@ -620,7 +818,7 @@ def importXLSX(user, xlsx_file):
 			)
 			db.session.add(teststepex)
 			db.session.commit()
-			app.logger.info(f'Created TestStepExecution id {teststepex.id} by {user}.')
+			app.logger.info(f'Created TestStepExecution id {teststepex.id} by {current_user}.')
 
 	return 1
 

+ 53 - 8
flask/app/views.py

@@ -64,13 +64,25 @@ def get_item(item_type, item_id):
 	return render_template('testrun/item.html', type=item_type, item=item)
 
 
-@app.route('/<string:item_type>/<int:item_id>/delete', methods=['POST'])
+@app.route('/<string:item_type>/<int:item_id>/delete', methods=['POST', 'DELETE'])
 @login_required
 def delete_item(item_type, item_id):
 	#
 	# delete item
 	#
+
+	# cascade delete
 	if request.method == 'POST':
+		try:
+			utils.deleteCascade(item_type, item_id)
+			flash(f'Item {utils.getItemType(item_type)} ID #{item_id} and its children have been successfully deleted.', 'success')
+			return 'Success', 200
+		except Exception as error:
+			flash(error, 'warning')
+			return 'Bad Request', 400
+
+	# delete single item
+	elif request.method == 'DELETE':
 		# get item by type and id
 		if item_type == 'testrun':
 			item = models.Testrun.query.get(item_id)
@@ -85,15 +97,17 @@ def delete_item(item_type, item_id):
 		else:
 			app.logger.warning(f'Item type "{item_type}" does not exist. Requested by "{current_user}".')
 			flash(f'Item type "{item_type}" does not exist.', 'warning')
-			return redirect(url_for('index'))
+			#return redirect(url_for('index'))
+			return 'Bad Request', 400
 
 		db.session.delete(item)
 		db.session.commit()
 		app.logger.info(f'Deleted {item_type} id {item_id} by {current_user}.')
-		flash(f'Item "{item.name}" successfully deleted.', 'success')
-		return redirect(url_for('item_list', item_type=item_type))
+		flash(f'Item "{item.name}" has been successfully deleted.', 'success')
+		#return redirect(url_for('item_list', item_type=item_type))
+		return 'Success', 200
 
-	return 'ERROR: Wrong request method'
+	return 'Method Not Allowed', 405
 
 
 @app.route('/<string:item_type>/<int:item_id>/edit', methods=['GET', 'POST'])
@@ -343,22 +357,53 @@ def import_testsun():
 	# imports testrun from file
 	#
 
-	# only import from XLSX is available now
+	# import only from XLSX is available now
 	form = forms.TestrunImportForm()
 
 	if form.validate_on_submit():
+		#utils.importXLSX(form.file.data)
 		try:
-			utils.importXLSX(current_user, form.file.data)
+			utils.importXLSX(form.file.data)
 			app.logger.info(f'Testrun successfully imported from "{form.file.data.filename}" by {current_user}.')
 			flash(f'Testrun successfully imported from "{form.file.data.filename}"', 'success')
 		except Exception as error:
-			app.logger.error(f'Fail to import Testrun from "{form.file.data.filename}" by {current_user}. {error}.')
+			app.logger.error(f'Failed to import Testrun from "{form.file.data.filename}" by {current_user}. {error}.')
 			flash(f'ERROR: Cannot import Testrun from "{form.file.data.filename}". {error}.', 'danger')
 	else:
 		flash(f'File is required for import', 'warning')
 
 	return redirect(url_for('item_list', item_type='testrun'))
 
+
+@app.route('/testrun/<int:item_id>/import', methods=['POST'])
+@login_required
+def update_testsun(item_id):
+	#
+	# imports testrun from file
+	#
+
+	# update only from XLSX is available now
+	form = forms.TestrunImportForm()
+
+	if form.validate_on_submit():
+		print('******** FORM:')
+		for field in form:
+			print(f'{field.name}:\t{field.data}')
+		# update items
+		#utils.importXLSX(form.file.data, item_id=item_id)
+		try:
+			utils.importXLSX(form.file.data, item_id=item_id)
+			app.logger.info(f'Testrun ID #{item_id} successfully updated from "{form.file.data.filename}" by {current_user}.')
+			flash(f'Testrun ID #{item_id} has been successfully updated from "{form.file.data.filename}"', 'success')
+		except Exception as error:
+			app.logger.error(f'Failed to update Testrun ID #{item_id} from "{form.file.data.filename}" by {current_user}. {error}.')
+			flash(f'ERROR: Cannot update Testrun ID #{item_id} from "{form.file.data.filename}". {error}.', 'danger')
+	else:
+		flash(f'File is required for import', 'warning')
+
+	return redirect(url_for('item_list', item_type='testrun'))
+	
+
 #
 # user authentication
 #