From 785bb48ff32a3ece43bef58e74c573ddf6f12411 Mon Sep 17 00:00:00 2001 From: Henrik Baran Date: Sun, 27 May 2018 12:27:22 +0200 Subject: [PATCH 01/14] sql drop views closes #91 - added drop if exists lines to sql file --- postgres/overview.sql | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/postgres/overview.sql b/postgres/overview.sql index 332cae8..df85ebf 100644 --- a/postgres/overview.sql +++ b/postgres/overview.sql @@ -1,3 +1,11 @@ +DROP VIEW IF EXISTS overview; +DROP VIEW IF EXISTS tmp_overview_loc_merged; +DROP VIEW IF EXISTS tmp_overview_box_loc; +DROP VIEW IF EXISTS tmp_overview_loc; +DROP VIEW IF EXISTS tmp_overview_box; +DROP VIEW IF EXISTS tmp_overview; + + CREATE OR REPLACE VIEW public.tmp_overview as SELECT b.box AS object, From 5c3347e46181003f33918dcd4ce11ac8e3e23fab Mon Sep 17 00:00:00 2001 From: Henrik Baran Date: Fri, 1 Jun 2018 23:16:55 +0200 Subject: [PATCH 02/14] export function closes #96 - added dynamic export view - adapted framework - changed js - added url --- lab/framework.py | 34 ++++++++++++++++++++++++++-------- lab/templates/lab/export.html | 6 +++++- lab/urls.py | 1 + lab/views.py | 15 +++++++++++++++ 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/lab/framework.py b/lab/framework.py index 6ea8cb5..1214ebb 100644 --- a/lab/framework.py +++ b/lab/framework.py @@ -641,14 +641,6 @@ def get(self, **dic): _list.append(tmp) return _list - @property - def export(self): - value_list = self.table.objects.values_list(*self.header) - _return = [tuple(custom.capitalize(self.header))] - for row in value_list: - _return.append(row) - return _return - class GetLog(GetStandard): pass @@ -770,6 +762,32 @@ def get(self, **dic): _list.append(tmp) return _list + @property + def export(self): + _query = self.query(order_by=self.order_by, type=self.type) + _list = list() + for row in _query: + _query_dynamic = self.query_dynamic(row['id']) + _tuple = tuple() + for field in self.header_start: + _tuple += (row[field], ) + + for field in self.type_attributes: + if field not in self.dynamic_table.objects.list_of_type_attributes(id_main=row['id']): + _tuple += ('', ) + else: + for row_dynamic in _query_dynamic: + if field == row_dynamic['type_attribute']: + _tuple += (row_dynamic['value'], ) + _tuple += (row['version'], ) + _list.append(_tuple) + _return = [tuple(custom.capitalize(self.header_start)) + + tuple(custom.capitalize(self.type_attributes)) + + ('Version', )] + for row in _list: + _return.append(row) + return _return + class GetDynamicAuditTrail(GetStandard): def __init__(self, table, dynamic_table, type): diff --git a/lab/templates/lab/export.html b/lab/templates/lab/export.html index e115bbc..ac2116c 100644 --- a/lab/templates/lab/export.html +++ b/lab/templates/lab/export.html @@ -18,6 +18,10 @@ \ No newline at end of file diff --git a/lab/urls.py b/lab/urls.py index 2a64ce5..29fa208 100644 --- a/lab/urls.py +++ b/lab/urls.py @@ -24,6 +24,7 @@ urlpatterns = [ # export url(r'^export/(?P\w+)/$', views.export, name='export'), + url(r'^export/(?P\w+)/(?P\w+)/$', views.export_reagents, name='export reagents'), # url(r'^import/(?P\w+)/$', views.import_data, name='import'), # others url(r'^offset/$', views.offset, name='offset'), diff --git a/lab/views.py b/lab/views.py index e142f3a..7f75d46 100644 --- a/lab/views.py +++ b/lab/views.py @@ -1686,6 +1686,21 @@ def export(request, dialog): return response +@require_GET +@login_required +@decorators.export_permission +def export_reagents(request, reagent, dialog): + queryset = framework.GetDynamic(table=models.Reagents, dynamic_table=models.DynamicReagents, type=reagent) + data = queryset.export + # write pseudo buffer for streaming + pseudo_buffer = custom.Echo() + writer = csv.writer(pseudo_buffer, delimiter=';') + # response + response = StreamingHttpResponse((writer.writerow(row) for row in data), content_type="text/csv") + response['Content-Disposition'] = 'attachment; filename="{}.csv"'.format(reagent) + return response + + """"@require_POST @login_required def import_data(request, dialog): From 37e564a84f47283d29f9526294b8bcbece12e600 Mon Sep 17 00:00:00 2001 From: Henrik Baran Date: Fri, 1 Jun 2018 23:41:58 +0200 Subject: [PATCH 03/14] search function shows no error if search item isn't found closes #99 - added simple text to indicate no search results --- lab/templates/lab/index.html | 3 +++ lab/templates/lab/js/search.js | 8 ++++++++ static/custom.css | 5 +++++ 3 files changed, 16 insertions(+) diff --git a/lab/templates/lab/index.html b/lab/templates/lab/index.html index a68b346..b67b8c5 100644 --- a/lab/templates/lab/index.html +++ b/lab/templates/lab/index.html @@ -365,6 +365,9 @@ {% endblock %} + {% if content != 'movement_log' and content != 'home' and content != 'login_log' %} -
+
Date: Tue, 5 Jun 2018 22:19:37 +0200 Subject: [PATCH 11/14] generic attribute audit trail closes #74 - altered audit trail html to pass in reagents attribute if available - enhanced framework to support functions to generate generic attributes audit trail - added function to model to support audit trail function - completed urls to suit new attribute - changed audit trail view for reagents to fulfill new functions --- lab/framework.py | 101 ++++++++++++++++++++++++------ lab/models.py | 7 ++- lab/templates/lab/audittrail.html | 2 +- lab/urls.py | 3 +- lab/views.py | 11 ++-- 5 files changed, 96 insertions(+), 28 deletions(-) diff --git a/lab/framework.py b/lab/framework.py index 8177e53..81cd04a 100644 --- a/lab/framework.py +++ b/lab/framework.py @@ -676,8 +676,8 @@ def _table_header_dynamic(self): def query_dynamic(self, id_main): """Query dynamic table for main record id. - :param id_ref: main record identifier id - :type id_ref: int + :param id_main: main record identifier id + :type id_main: int :return: records for id_ref :rtype: django.db.models.query.QuerySet @@ -810,49 +810,110 @@ def export(self): return _return -class GetDynamicAuditTrail(GetStandard): - def __init__(self, table, dynamic_table, type): - super().__init__(table) - self.type = type +class GetDynamicAuditTrail(GetDynamic): + def __init__(self, table, dynamic_table, type, dt=None): + super().__init__(table, dynamic_table, type) + if dt: + self.dt = datetime.timedelta(seconds=int(dt) * 60) + self.utc_offset = custom.fill_up_time_delta(dt) + + def query_dynamic_at(self, id_main, version): + """Query dynamic table for main record id. + + :param id_main: main record identifier id + :type id_main: int + + :param version: version of the audit trail record stored in id_ref + :type version: int + + :return: records for id_ref + :rtype: django.db.models.query.QuerySet + """ + return self.dynamic_table.objects.filter(id_main=id_main, id_ref=version).values() + + @property + def header(self): + _header = self._table_header + _header.remove('id') + _header.remove('checksum') + _header.remove('id_ref') + return _header + + @property + def header_start(self): + _header_start = self.header + if self.affiliation == 'Reagents': + _header_start.remove('type') + _header_start.remove('version') + _header_start.remove('action') + _header_start.remove('user') + _header_start.remove('timestamp') + return _header_start + + @property + def header_dynamic(self): + _header_dynamic = self._table_header_dynamic + _header_dynamic.remove('id') + _header_dynamic.remove('checksum') + _header_dynamic.remove('id_ref') + # _header_dynamic.remove('id_main') + return _header_dynamic + + @property + def html_header(self): + _header = self.header_start + self.type_attributes + _header.append('Version') + _header.append('action') + _header.append('user') + _header.append('timestamp') + return custom.capitalize(_header) def table_row_head_total(self, row, query_dynamic): - # TODO #59 if self.verify_checksum(row=row): + _success_list = list() for row in query_dynamic: if not self.verify_checksum_dynamic(row=row): - return '' - return '' + _success_list.append(False) + else: + _success_list.append(True) + if custom.check_equal(_success_list): + return '' + else: + return '' else: return '' def get(self, **dic): - _query = self.query(order_by=self.order_by, type=self.type) + _query = self.query(order_by=self.order_by, type=self.type, **dic) _list = list() for row in _query: - _query_dynamic = self.query_dynamic(row['id']) + _query_dynamic = self.query_dynamic_at(id_main=row['id_ref'], version=row['version']) tmp = self.table_row_head_total(row=row, query_dynamic=_query_dynamic) # adding all tds for builder_header_start for field in self.header_start: - # tagging the unique field - if field == self.unique: - tmp += '{}'.format(row[field]) - else: - tmp += '{}'.format(row[field]) + # only payload + tmp += '{}'.format(row[field]) # adding all tds for builder_header_dynamic for field in self.type_attributes: - if field not in self.dynamic_table.objects.list_of_type_attributes(id_main=row['id']): - tmp += '' + if field not in self.dynamic_table.objects.list_of_type_attributes(id_main=row['id_ref'], + version=row['version']): + tmp += '' else: for row_dynamic in _query_dynamic: if field == row_dynamic['type_attribute']: - tmp += '{}'.format(row_dynamic['value']) + tmp += '{}'.format(row_dynamic['value']) # adding all tds for builder_header_end tmp += '{}'.format(row['version']) + tmp += '{}'.format(row['action']) + tmp += '{}'.format(row['user']) + tmp += '{}'.format(custom.format_timestamp(timestamp=row['timestamp'], + dt=self.dt, + utc_offset=self.utc_offset)) # close table tmp += '' # append table row _list.append(tmp) - return _list + return True, _list class TableManipulation(Master): diff --git a/lab/models.py b/lab/models.py index d27e476..f07b0b7 100644 --- a/lab/models.py +++ b/lab/models.py @@ -387,7 +387,12 @@ class DynamicReagents(models.Model): # audit trail manager class DynamicReagentsAuditTrailManager(GlobalAuditTrailManager): - pass + def list_of_type_attributes(self, id_main, version): + try: + return list(self.filter(id_main=id_main, id_ref=version).order_by('id'). + values_list('type_attribute', flat=True)) + except IndexError: + return list() # audit trail table diff --git a/lab/templates/lab/audittrail.html b/lab/templates/lab/audittrail.html index 4a6b3b7..cb20414 100644 --- a/lab/templates/lab/audittrail.html +++ b/lab/templates/lab/audittrail.html @@ -23,7 +23,7 @@ var item = $('#id_table').find('#id_unique').find("td:first").text(); $.ajax({ method: "GET", - url: "/" + "{{ content }}" + "/audit_trail/", + url: "/" + "{{ content }}" + "/audit_trail/{% if content_dynamic %}{{ content_dynamic }}/{% endif %}", data: { 'unique': item }, diff --git a/lab/urls.py b/lab/urls.py index 29fa208..22cf310 100644 --- a/lab/urls.py +++ b/lab/urls.py @@ -75,9 +75,8 @@ # reagents url(r'^reagents/new/(?P\w+)/$', views.reagents_new, name='reagents new'), url(r'^reagents/edit/(?P\w+)/$', views.reagents_edit, name='reagents edit'), - # url(r'^reagents/edit/$', views.reagents_edit, name='reagents edit'), url(r'^reagents/delete/$', views.reagents_delete, name='reagents delete'), - url(r'^reagents/audit_trail/$', views.reagents_audit_trail, name='reagents audit trail'), + url(r'^reagents/audit_trail/(?P\w+)/$', views.reagents_audit_trail, name='reagents audit trail'), url(r'^reagents/label/$', views.reagents_label, name='reagents label'), url(r'^reagents/(?P\w+)/$', views.reagents, name='reagents'), # logs diff --git a/lab/views.py b/lab/views.py index 9f611d2..b84615f 100644 --- a/lab/views.py +++ b/lab/views.py @@ -1183,7 +1183,9 @@ def reagents(request, reagent): 'user': request.user.username, 'perm': request.user.permissions}, get_standard=framework.GetDynamic(table=models.Reagents, dynamic_table=models.DynamicReagents, type=reagent), - get_audit_trail=framework.GetAuditTrail(table=models.ReagentsAuditTrail, dt=request.session['offset']), + get_audit_trail=framework.GetDynamicAuditTrail(table=models.ReagentsAuditTrail, + dynamic_table=models.DynamicReagentsAuditTrail, + type=reagent, dt=request.session['offset']), form_render_new=forms.ReagentsFormNew(), form_render_edit=forms.ReagentsFormEdit()) return render(request, 'lab/index.html', context) @@ -1193,9 +1195,10 @@ def reagents(request, reagent): @login_required @decorators.permission('re_r', 're_w', 're_d', 're_l') @decorators.require_ajax -def reagents_audit_trail(request): - response, data = framework.GetAuditTrail( - table=models.ReagentsAuditTrail, dt=request.session['offset']).get( +def reagents_audit_trail(request, reagent): + response, data = framework.GetDynamicAuditTrail(table=models.ReagentsAuditTrail, + dynamic_table=models.DynamicReagentsAuditTrail, + type=reagent, dt=request.session['offset']).get( id_ref=models.Reagents.objects.id(request.GET.get('unique'))) data = {'response': response, 'data': data} From cc63af518d4ece88b560e4b68fc0a43b87020a89 Mon Sep 17 00:00:00 2001 From: Henrik Baran Date: Tue, 5 Jun 2018 22:28:06 +0200 Subject: [PATCH 12/14] inefficient js loads closes #105 - changed load sequence of js/html files --- lab/templates/lab/index.html | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/lab/templates/lab/index.html b/lab/templates/lab/index.html index cf313da..afd1037 100644 --- a/lab/templates/lab/index.html +++ b/lab/templates/lab/index.html @@ -837,15 +837,17 @@ - {% include "lab/sidebar.html" %} {% include "lab/logout.html" %} - {% include "lab/export.html" %} - {% include "lab/import.html" %} {% if tables %} + {% include "lab/export.html" %} + {% include "lab/import.html" %} + {% include "lab/sidebar.html" %} + {% if content != 'movement_log' and content != 'home' and content != 'login_log' and content != 'boxing_log' %} {% include "lab/audittrail.html" %} {% include "lab/new.html" %} @@ -860,12 +862,6 @@ {% include "lab/label.html" %} {% endif %} - {% if content == 'home' %} - {% include "lab/movement.html" %} - {% include "lab/boxing.html" %} - {% include "lab/form.html" %} - {% endif %} - {% if content == 'overview' %} {% include "lab/overview_boxing.html" %} {% include "lab/movement.html" %} From d7f27ce53c3425b807fee7e684fe058ba0561d83 Mon Sep 17 00:00:00 2001 From: Henrik Baran Date: Tue, 5 Jun 2018 23:06:36 +0200 Subject: [PATCH 13/14] non-dedicated boxes not listed closes #66 - improved box locating function to consider non-dedicated boxes - kept priority of dedicated boxes over non-dedicated - if no position available no box/position/location displayed --- lab/views.py | 90 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 25 deletions(-) diff --git a/lab/views.py b/lab/views.py index b84615f..a9481a8 100644 --- a/lab/views.py +++ b/lab/views.py @@ -1616,35 +1616,75 @@ def overview_locate(request): return JsonResponse(data) _type = models.Overview.objects.type(unique=request.GET.get('unique')) box_count = models.Boxes.objects.count_box_by_type(type=_type) - if box_count == 0: - box = '---' - location = '---' - position = '---' + count_mixed = models.Boxes.objects.count_box_by_type(type='') + if box_count == 0 and count_mixed == 0: data = {'response': True, - 'location': location, - 'box': box, - 'position': position} + 'location': '---', + 'box': '---', + 'position': '---'} return JsonResponse(data) - for x in range(box_count): - box = models.Boxes.objects.box_by_type(type=_type, count=x) - position = models.Boxing.objects.next_position(box=box[:7]) - location = models.Overview.objects.location(unique=box[:7]) - if not location: - location = '---' - if position: - data = {'response': True, - 'location': location, - 'box': box, - 'position': position} - return JsonResponse(data) - else: - if x == box_count: - position = '---' + else: + # if dedicated boxes exist first check them + if box_count > 0: + # check dedicated boxes + for x in range(box_count): + box = models.Boxes.objects.box_by_type(type=_type, count=x) + position = models.Boxing.objects.next_position(box=box[:7]) + location = models.Overview.objects.location(unique=box[:7]) + if not location: + location = '---' + if position: + data = {'response': True, + 'location': location, + 'box': box, + 'position': position} + return JsonResponse(data) + # if no return happened, check if mixes boxes exist + if count_mixed > 0: + for x in range(count_mixed): + box = models.Boxes.objects.box_by_type(type='', count=x) + position = models.Boxing.objects.next_position(box=box[:7]) + location = models.Overview.objects.location(unique=box[:7]) + if not location: + location = '---' + if position: + data = {'response': True, + 'location': location, + 'box': box, + 'position': position} + return JsonResponse(data) + # no position in mixed boxes return nothing data = {'response': True, - 'location': location, - 'box': box, - 'position': position} + 'location': '---', + 'box': '---', + 'position': '---'} return JsonResponse(data) + # return nothing if no mixed boxes available + else: + data = {'response': True, + 'location': '---', + 'box': '---', + 'position': '---'} + return JsonResponse(data) + if count_mixed > 0: + for x in range(count_mixed): + box = models.Boxes.objects.box_by_type(type='', count=x) + position = models.Boxing.objects.next_position(box=box[:7]) + location = models.Overview.objects.location(unique=box[:7]) + if not location: + location = '---' + if position: + data = {'response': True, + 'location': location, + 'box': box, + 'position': position} + return JsonResponse(data) + # no position in mixed boxes return nothing + data = {'response': True, + 'location': '---', + 'box': '---', + 'position': '---'} + return JsonResponse(data) @require_POST From 0f433b89878f74c8bd79baf7325f6f73287b42be Mon Sep 17 00:00:00 2001 From: Henrik Baran Date: Tue, 5 Jun 2018 23:11:53 +0200 Subject: [PATCH 14/14] setting & hardening #101 - adapted version --- lab/templates/lab/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lab/templates/lab/index.html b/lab/templates/lab/index.html index afd1037..6ed7372 100644 --- a/lab/templates/lab/index.html +++ b/lab/templates/lab/index.html @@ -122,7 +122,7 @@
{% endif %}