diff --git a/README.md b/README.md index 997e7c3..905f61f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ ### Development Installation django-admin startproject languagestrings -cd languagestrings sudo docker-compose up -d sudo docker exec -it web_1 python manage.py startapp strings sudo chown -R $USER:$USER . @@ -13,3 +12,4 @@ sudo chown -R $USER:$USER . docker exec -it languagestrings_web_1 python manage.py createsuperuser 2. Create Group with WebApp via Admin 3. Import any gaap data using import from Web + diff --git a/src/languagestrings/settings.py b/src/languagestrings/settings.py index b1b43dc..73bd227 100644 --- a/src/languagestrings/settings.py +++ b/src/languagestrings/settings.py @@ -29,7 +29,7 @@ ALLOWED_HOSTS = ['*'] # Application definition -APP_NAME = 'BETAGO' +APP_NAME = 'SPREADSHEET' VERSION = "1.8.0-dev" DESCRIPTION = "Localization Text Management Application" @@ -217,4 +217,4 @@ ''' Uploaded Excel Files ''' -MEDIA_ROOT = '/data/uploads' \ No newline at end of file +MEDIA_ROOT = '/data/uploads' diff --git a/src/strings/templates/strings/js/commonFunction.js b/src/strings/templates/strings/js/commonFunction.js index 538d7fb..641bec6 100644 --- a/src/strings/templates/strings/js/commonFunction.js +++ b/src/strings/templates/strings/js/commonFunction.js @@ -5,21 +5,22 @@ var active=false; var hTables = {}; var tempData = {}; var title; +var confirmResetHandsonTable; var confirmHandsonTable; var badHtmlHandsonTable; var savedHandsonTable; -var mainPanel; -var confirmPanel; -var badHtmlPanel; -var savedPanel; -var notSavedPanel; +var hotNotSavedTable; var productId; var save; var confirm; var cancel; var days; var language; -var hotConfirm; +var hotBadElem; +var hotConfirmElem; +var hotResetElem; +var hotSavedElem; +var hotNotSavedElem; function getCookie(name) { var cookieValue = null; @@ -38,20 +39,22 @@ function getCookie(name) { } function confirm() { // save all cell's data - var tablename = $("#tablename").val(); - var url = '/localtext/products/'+$("#productId").val()+'/confirm'; + const tablename = $("#tablename").val(); + const url = '/localtext/products/'+$("#productId").val()+'/confirm'; + const save = hotConfirmElem.getData(); + const reset = hotResetElem.getData(); $.ajax({ url, type: 'POST', - data: JSON.stringify(hotConfirm.getData()), + data: JSON.stringify(save.concat(reset)), "beforeSend": function(xhr, settings) { $.ajaxSettings.beforeSend(xhr, settings); }, success: function (results) { showTab(document.getElementById('tab-review')); // update page loads - hotSavedTable.loadData(results.saved); - hotNotSavedTable.loadData(results.notSaved); + hotSavedElem.loadData(results.saved); + hotNotSavedElem.loadData(results.notSaved); }, error: function(error) { alert("Error: " + error.statusText); @@ -71,14 +74,15 @@ function save(elem, pk, product, category) { $.ajaxSettings.beforeSend(xhr, settings); }, success: function (results) { - var tab = document.getElementById('tab-saving'); - showTab(tab); - hotConfirm.loadData(results.data); - if (!results.data.length) { + if (!results.data.length && !results.reset.length) { $("#confirm").hide(); } + var tab = document.getElementById('tab-saving'); + showTab(tab); // 4. errors - hotBadHtml.loadData(results.error); + hotConfirmElem.loadData(results.data); + hotResetElem.loadData(results.reset); + hotBadElem.loadData(results.error); $("#productId").val(results.product); $("#tablename").val(category); }, @@ -271,15 +275,16 @@ function editedCellRenderer(instance, td, row, col, prop, value, cellProperties) Handsontable.renderers.registerRenderer('editedCellRenderer', editedCellRenderer); function customCellRenderer(instance, td, row, col, prop, value, cellProperties) { Handsontable.renderers.TextRenderer.apply(this, arguments); - if (col === 5 && {% if editEnglish %}false{% else %}true{%endif %}) { - return; - } if (!value || value === '') { - td.style.background = '#EEEEEE'; + td.style.background = 'silver'; return; } if (col > 4 && !hiddenColumns.includes(col) && new Date(instance.getDataAtCell(row, col + 1)) <= new Date(instance.getDataAtCell(row, 4)) ) { - td.style.background = '#FF0000'; + td.style.background = 'red'; + } + if (cellProperties.isModified === true) { + td.style.background = 'blue'; + td.style.color = 'white'; } }; Handsontable.renderers.registerRenderer('customCellRenderer', customCellRenderer); @@ -288,7 +293,15 @@ function plainRenderer(instance, td, row, col, prop, value, cellProperties) { td.className = 'plain'; }; Handsontable.renderers.registerRenderer('plainRenderer', plainRenderer); - +function scrubRenderer(instance, td, row, col, prop, value, cellProperties) { + Handsontable.renderers.TextRenderer.apply(this, arguments); + const pattern = new RegExp(/(\w+)_modified_at/, 'i'); + const m = pattern.exec(value); + if (m) { + td.innerHTML = m[1]; + } +}; +Handsontable.renderers.registerRenderer('scrubRenderer', scrubRenderer); function encodeQueryData(data) { var ret = []; for (let d in data) diff --git a/src/strings/templates/strings/js/handsonTables.js b/src/strings/templates/strings/js/handsonTables.js index f4f5164..7b6582e 100644 --- a/src/strings/templates/strings/js/handsonTables.js +++ b/src/strings/templates/strings/js/handsonTables.js @@ -4,15 +4,11 @@ var $$ = function(id) { title = $$('title'), // handson tables confirmHandsonTable = $$('confirmHandsonTable'), +confirmResetHandsonTable = $$('confirmResetHandsonTable'), badHtmlHandsonTable = $$('badHtmlHandsonTable'), savedHandsonTable = $$('savedHandsonTable'), notSavedHandsonTable = $$('notSavedHandsonTable'), -// panels -mainPanel = $$('mainPanel'), -confirmPanel = $$('confirmPanel'), -badHtmlPanel = $$('badHtmlPanel'), -savedPanel = $$('savedPanel'), -notSavedPanel = $$('notSavedPanel'), + productId = $$('productId'), save = $$('save'), confirm = $$('confirm'), @@ -26,7 +22,7 @@ $('input:file').change(function(){ } }); -hotSavedTable = new Handsontable(savedHandsonTable, { +hotSavedElem = new Handsontable(savedHandsonTable, { colHeaders: ['ID', 'Language', 'Before', 'After'], rowHeaders: true, manualColumnResize: [300, 100, 300, 300], @@ -56,8 +52,7 @@ hotSavedTable = new Handsontable(savedHandsonTable, { }, ] }); - -hotNotSavedTable = new Handsontable(notSavedHandsonTable, { +hotNotSavedElem = new Handsontable(notSavedHandsonTable, { colHeaders: ['ID', 'Language', 'Before', 'After', 'Errors'], rowHeaders: true, manualColumnResize: [300, 100, 200, 200, 200], @@ -93,7 +88,7 @@ hotNotSavedTable = new Handsontable(notSavedHandsonTable, { ] }); -hotBadHtml = new Handsontable(badHtmlHandsonTable, { +hotBadElem = new Handsontable(badHtmlHandsonTable, { colHeaders: ['ID', 'Language', 'Before', 'After', 'Reason'], rowHeaders: true, manualColumnResize: [300, 100, 200, 200, 200], @@ -128,8 +123,37 @@ hotBadHtml = new Handsontable(badHtmlHandsonTable, { } ] }); +hotResetElem = new Handsontable(confirmResetHandsonTable, { + colHeaders: ['ID', 'Language', 'Previous', 'Timestamp'], + rowHeaders: true, + manualColumnResize: [300, 100, 300], + manualRowResize: true, + fixedColumnsLeft: 1, + stretchH: 'last', + preventOverflow: 'horizontal', + currentRowClassName: 'currentRow', + columns: [ + { + data: 'stringid', + readOnly: true, + }, + { + data: 'field', + readOnly: true, + renderer: 'scrubRenderer' + }, + { + data: 'before', + readOnly: true, + }, + { + data: 'after', + readOnly: true, + } + ] +}); -hotConfirm = new Handsontable(confirmHandsonTable, { +hotConfirmElem = new Handsontable(confirmHandsonTable, { colHeaders: ['ID', 'Language', 'Before', 'After'], rowHeaders: true, manualColumnResize: true, @@ -206,19 +230,7 @@ hTables["{{ product.category }}"] = new Handsontable($$('handsonTable_'+ "{{ pro preventOverflow: 'both', currentRowClassName: 'currentRow', columnSorting: true, - beforeColumnSort: function (col, order) { - sortTable('{{ product.category }}', col, order, function() { - return false; - }); - }, sortIndicator: true, - cells: function (row, col, prop) { - var cellProperties = {}; - if (col > 2) { - cellProperties.renderer = "customCellRenderer"; - } - return cellProperties; - }, columns: [ { data: 'stringid', @@ -234,14 +246,16 @@ hTables["{{ product.category }}"] = new Handsontable($$('handsonTable_'+ "{{ pro }, { data: 'Korea', - {% if not editKorea %}readOnly: true{% endif %} + {% if editKorea %}renderer: "customCellRenderer" + {% else %}readOnly: true{% endif %} }, { data: 'Korea_modified_at' }, { data: 'English', - {% if not editEnglish %}readOnly: true{% endif %} + {% if editEnglish %}renderer: "customCellRenderer" + {% else %}readOnly: true{% endif %} }, { data: 'English_modified_at' @@ -249,38 +263,56 @@ hTables["{{ product.category }}"] = new Handsontable($$('handsonTable_'+ "{{ pro {% for name in publishers %} ,{ data: '{{ name }}', + renderer: "customCellRenderer" }, { data: '{{ name }}_modified_at' } {% endfor %} ], - afterChange: function (change, source) { + afterSelection: function(row, col, row2, col2) { + const instance = this; + const skip = instance.propToCol('Korea'); + const after = new Date().toISOString(); + for (let i = row; i <= row2; i++) { + for (let j = col; j <= col2; j+=2) { + if (skip >= j) continue; + const field = instance.colToProp(j+1); + const stringid = instance.getDataAtCell(i, 0); + const before = instance.getDataAtCell(i, j+1); + instance.setDataAtCell(i, j+1, after); + if (instance.getDataAtCell(i, j) !== '') { + tempData["{{ product.category }}"][`${stringid}.${field}`] = { stringid, field, before, after }; + } + } + } + }, + afterChange: function(changes, source) { if (source === 'loadData') { return; //don't save this change } - if (change !== null) { - var instance = this; - $(change).each(function () { - // change the color of edited cell - // changedData = [row, col, before, after] - var changedData = this; - if (changedData[2] === changedData[3]) { - return; // don't do anything is no change - } - var cell = instance.getCell(changedData[0], instance.propToCol(changedData[1])); - if (cell) { - cell.style.color = '#0000FF'; - } - // replace the row with stringid - var stringid = instance.getDataAtCell(changedData[0], 0); - if (tempData["{{ product.category }}"][stringid+'.'+changedData[1]]) { - changedData[2] = tempData["{{ product.category }}"][stringid+'.'+changedData[1]]['before']; - } - // should change the color of cell??? - tempData["{{ product.category }}"][stringid+'.'+changedData[1]] = { 'stringid': stringid, 'field': changedData[1], 'before': changedData[2], 'after': changedData[3] }; - }); - } + //"changes" is a 2D array + const instance = this; + changes.forEach(([row, prop, before, after]) => { + const col = instance.propToCol(prop); + if (hiddenColumns.includes(col)) { + return; + } + if (before === after) { + return; // don't do anything is no change + } + //mark cell as modified + const cellProperties = instance.getCellMeta(row, col); + cellProperties.isModified = true; + // store changed data + const stringid = instance.getDataAtCell(row, 0); + if (tempData["{{ product.category }}"][`${stringid}.${prop}`]) { + before = tempData["{{ product.category }}"][`${stringid}.${prop}`]['before']; + } + // should change the color of cell??? + tempData["{{ product.category }}"][`${stringid}.${prop}`] = { stringid, field: prop, before, after }; + }); + instance.render(); } }); {% endfor %} diff --git a/src/strings/templates/strings/save.html b/src/strings/templates/strings/save.html index 32b5ea3..4d45885 100644 --- a/src/strings/templates/strings/save.html +++ b/src/strings/templates/strings/save.html @@ -28,6 +28,20 @@ +
+
+ Reset Timestamp +
+ Cancel +
+
+ +
+
+
+ +
+
diff --git a/src/strings/views.py b/src/strings/views.py index aacd31c..92a1607 100644 --- a/src/strings/views.py +++ b/src/strings/views.py @@ -313,10 +313,12 @@ def confirm(request, pk): ''' Process all parameters ''' + tablename = "{}_{}".format(document.name, document.category) # Always return an HttpResponseRedirect after successfully dealing # with POST data. This prevents data from being posted twice if a # user hits the Back button. saved = [] + reset = [] notSaved = [] notFound = [] logger.info('Confirmed Raw data: "%s"' % request.body.decode("utf-8")) @@ -327,25 +329,37 @@ def confirm(request, pk): before = item[2] after = item[3] try: - languagestring = PRODUCTS["{0}_{1}".format(document.name, document.category)].objects.get(pk=stringid) + languagestring = PRODUCTS[tablename].objects.get(pk=stringid) stored = getattr(languagestring, attribute) - if (stored == before): + if type(stored) == datetime.datetime: + after = timezone.now(); + item[3] = '{}'.format(after) + try: + setattr(languagestring, attribute, after) + languagestring.save() + # confirmation + reset.append(item) + except Exception as e: + logger.error('Unabled to Save "{}", "{}"'.format(item,e)) + item.append(str(e)) + notSaved.append(item) + elif (stored == before): setattr(languagestring, attribute, after) try: languagestring.save() # confirmation saved.append(item) except Exception as e: - logger.error('Unabled to Save "{0}", "{1}"'.format(item,e)) + logger.error('Unabled to Save "{}", "{}"'.format(item,e)) item.append(str(e)) notSaved.append(item) else: # field value has been changed... must be verified - logger.error('Database value has changed from previous. expected:{0}::stored:{1}'.format(before, stored)) - item.append("Current Server Data is different from before data - " + stored) + logger.error('Database value has changed from previous. expected:{}::stored:{}'.format(before, stored)) + item.append('Current Server Data is different from before data - {}'.format(stored)) notSaved.append(item) - except (KeyError, PRODUCTS["{0}_{1}".format(document.name, document.category)].DoesNotExist) as e: - logger.error('Unable to retrieve record:{0}'.format(e)) + except (KeyError, PRODUCTS[tablename].DoesNotExist) as e: + logger.error('Unable to retrieve record:{}'.format(e)) item.append(e) notFound.append(item) if (len(notSaved)): @@ -359,11 +373,11 @@ def confirm(request, pk): #print svnMgr.diff() context = { 'saved': saved, + 'reset': reset, 'notSaved': notSaved, 'notFound': notFound, 'returncode': returncode, 'product': pk, - # 'diff': svnMgr.diff(), } return HttpResponse(json.dumps(context), content_type="application/json") @@ -393,6 +407,7 @@ def save(request, pk): except: return Http404 + reset = [] result = [] errors = [] abnormal = {} @@ -405,29 +420,32 @@ def save(request, pk): for stringidField, value in data.iteritems(): stringid = value['stringid'] field = value['field'] - + languagestring = PRODUCTS["{0}_{1}".format(document.name, document.category)].objects.get(pk=stringid) translation = getattr(languagestring, field) - if (translation != value['before']): - # field value has been changed... must be verified - abnormal[stringid] = value - abnormal[stringid]['server'] = translation - continue - - if (translation == value['after']): - continue - - # validate html - value['parsed'], err = validateHTMLFragment(value['after']) - if (err): - logger.error('[ERROR] HTML Validation: "%s"' % err) - value['reason'] = err; - errors.append(value) + if type(translation) == datetime.datetime: + value['after'] = '{}'.format(timezone.now()) + reset.append(value) else: - result.append(value) - + if (translation != value['before']): + # field value has been changed... must be verified + abnormal[stringid] = value + abnormal[stringid]['server'] = translation + continue + if (translation == value['after']): + continue + # validate html + value['parsed'], err = validateHTMLFragment(value['after']) + if (err): + logger.error('[ERROR] HTML Validation: "%s"' % err) + value['reason'] = err; + errors.append(value) + else: + result.append(value) + logger.info('Response: "%s"' % value); context = { 'data': result, + 'reset': reset, 'error': errors, 'product': pk, } @@ -551,4 +569,4 @@ class GroupViewSet(viewsets.ModelViewSet): class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer - pagination_class = StandardResultsSetPagination \ No newline at end of file + pagination_class = StandardResultsSetPagination