+
+
no records have been found ...
+
{% if content != 'movement_log' and content != 'home' and content != 'login_log' %}
@@ -830,17 +833,21 @@
Password
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
crossorigin="anonymous">
-
- {% include "lab/core.html" %}
- {% include "lab/sidebar.html" %}
- {% include "lab/logout.html" %}
- {% include "lab/export.html" %}
- {% include "lab/import.html" %}
+
+ {% include "lab/logout.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" %}
@@ -855,12 +862,6 @@ Password
{% 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" %}
diff --git a/lab/templates/lab/js/core.js b/lab/templates/lab/js/core.js
new file mode 100644
index 0000000..1eaf33d
--- /dev/null
+++ b/lab/templates/lab/js/core.js
@@ -0,0 +1,185 @@
+/*
+turtle-lab.org
+Copyright (C) 2018 Henrik Baran
+
+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 .
+*/
+
+
+// cover
+$(window).on('load', function() {
+ $("#cover").hide();
+});
+
+// VARIABLES
+var table_row_color = "#98ccff";
+var count_selected, i, index_unique, index_selected_old, index_selected_actual, all_tr, has_class;
+
+//table
+var table = $('#id_table');
+
+// buttons
+var button_delete = $("#id_nav_btn_delete");
+var button_edit = $("#id_nav_btn_edit");
+var button_duplicate = $("#id_nav_btn_duplicate");
+var button_log_record = $("#id_nav_btn_audit_trail");
+var button_movement = $("#id_nav_btn_movement");
+var button_boxing = $("#id_nav_btn_overview_boxing");
+var button_barcode = $("#id_nav_btn_barcode");
+var button_password = $("#id_nav_btn_password");
+var button_active = $("#id_nav_btn_active");
+
+// functions for button control
+function enable_all() {
+ button_delete.removeClass('disabled').removeClass('btn-default').addClass('btn-danger');
+ button_edit.removeClass('disabled').removeClass('btn-default').addClass('btn-primary');
+ button_duplicate.removeClass('disabled').removeClass('btn-default').addClass('btn-success');
+ button_log_record.removeClass('disabled').removeClass('btn-default').addClass('btn-warning');
+ button_movement.removeClass('disabled').removeClass('btn-default').addClass('btn-info');
+ button_boxing.removeClass('disabled').removeClass('btn-default').addClass('btn-primary');
+ button_barcode.removeClass('disabled').removeClass('btn-default').addClass('btn-info');
+ button_password.removeClass('disabled').removeClass('btn-default').addClass('btn-info');
+ button_active.removeClass('disabled').removeClass('btn-default').addClass('btn-success');
+}
+
+function disable_all(del) {
+ if (!del) {
+ button_delete.addClass('disabled').removeClass('btn-danger').addClass('btn-default');
+ }
+ button_edit.addClass('disabled').removeClass('btn-primary').addClass('btn-default');
+ button_duplicate.addClass('disabled').removeClass('btn-success').addClass('btn-default');
+ button_log_record.addClass('disabled').removeClass('btn-warning').addClass('btn-default');
+ button_movement.addClass('disabled').removeClass('btn-info').addClass('btn-default');
+ button_boxing.addClass('disabled').removeClass('btn-primary').addClass('btn-default');
+ button_barcode.addClass('disabled').removeClass('btn-info').addClass('btn-default');
+ button_password.addClass('disabled').removeClass('btn-info').addClass('btn-default');
+ button_active.addClass('disabled').removeClass('btn-success').addClass('btn-default');
+}
+
+// click handling
+$(table).find('tbody tr').mousedown (function(event) {
+ count_selected = $('.selected').length;
+ event.preventDefault();
+ document.getSelection().removeAllRanges();
+ index_selected_old = $(table).find('.selected').index();
+ enable_all();
+ if (event.ctrlKey) {
+ if (count_selected === 0) {
+ $(this).addClass('selected').css("background-color", table_row_color).attr('id', 'id_unique');
+ } else {
+ if ($(this).hasClass('selected')) {
+ $(this).removeClass('selected').css("background-color", "").attr('id', '');
+ count_selected = $('.selected').length;
+ if (count_selected > 1) {
+ disable_all(true);
+ } else if (count_selected === 1) {
+ enable_all();
+ } else {
+ disable_all();
+ }
+ } else {
+ $(this).addClass('selected').css("background-color", table_row_color);
+ disable_all(true);
+ }
+ }
+ } else if (event.shiftKey) {
+ // all until next selected
+ index_selected_actual = $(this).index();
+ index_unique = $(table).find('#id_unique').index();
+ // just click
+ if (count_selected === 0) {
+ $(this).addClass('selected').css("background-color", table_row_color).attr('id', 'id_unique');
+ } else {
+ // reduce
+ if ($(this).hasClass('selected')) {
+ // above
+ if (index_selected_actual < index_unique) {
+ all_tr = $(this).prevAll('tr').addBack();
+ for (i = 0; i < index_selected_actual; i++) {
+ $(all_tr[i]).removeClass('selected').css("background-color", "");
+ }
+ disable_all(true);
+ // below
+ } else if (index_selected_actual > index_unique) {
+ all_tr = $(this).nextAll('tr').addBack();
+ for (i = 1; i < all_tr.length; i++) {
+ $(all_tr[i]).removeClass('selected').css("background-color", "");
+ }
+ disable_all(true);
+ // equal
+ } else if (index_selected_actual === index_unique) {
+ $(this).siblings().removeClass('selected').css("background-color", "");
+ if (count_selected > 1) {
+ enable_all();
+ } else {
+ $(this).removeClass('selected').css("background-color", "").attr('id', '');
+ disable_all();
+ }
+ }
+ // add
+ } else {
+ // above
+ if (index_selected_actual < index_selected_old) {
+ // first remove all below
+ all_tr = $(table).find('#id_unique').nextAll('tr');
+ for (i = 0; i < all_tr.length; i++) {
+ $(all_tr[i]).removeClass('selected').css("background-color", "");
+ }
+ // then add all above
+ all_tr = $(this).nextAll('tr').addBack();
+ for (i = 0; i < all_tr.length; i++) {
+ has_class = $(all_tr[i]).attr('class');
+ if (has_class !== 'selected') {
+ $(all_tr[i]).addClass('selected').css("background-color", table_row_color);
+ } else {
+ break;
+ }
+ }
+ // below
+ } else if (index_selected_actual > index_selected_old) {
+ // first remove all above
+ all_tr = $(table).find('#id_unique').prevAll('tr');
+ for (i = 0; i < index_unique; i++) {
+ $(all_tr[i]).removeClass('selected').css("background-color", "");
+ }
+ // then add all below
+ all_tr = $(this).prevAll('tr').addBack();
+ for (i = all_tr.length; i > 0; i--) {
+ has_class = $(all_tr[i]).attr('class');
+ if (has_class !== 'selected') {
+ $(all_tr[i]).addClass('selected').css("background-color", table_row_color);
+ } else {
+ break;
+ }
+ }
+ }
+ disable_all(true);
+ }
+ }
+ } else {
+ if ($(this).hasClass('selected')) {
+ $(this).removeClass('selected').css("background-color", "").attr('id', '');
+ count_selected = $('.selected').length;
+ if (count_selected > 0) {
+ $(this).addClass('selected').siblings().removeClass('selected').attr('id', '').css("background-color", "");
+ $(this).css("background-color", table_row_color).attr('id', 'id_unique');
+ } else {
+ disable_all();
+ }
+ } else {
+ $(this).addClass('selected').siblings().removeClass('selected').attr('id', '').css("background-color", "");
+ $(this).css("background-color", table_row_color).attr('id', 'id_unique');
+ }
+ }
+});
\ No newline at end of file
diff --git a/lab/templates/lab/js/csrf.js b/lab/templates/lab/js/csrf.js
new file mode 100644
index 0000000..9432259
--- /dev/null
+++ b/lab/templates/lab/js/csrf.js
@@ -0,0 +1,35 @@
+/*
+turtle-lab.org
+Copyright (C) 2018 Henrik Baran
+
+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 .
+*/
+
+
+// define csrf token according to https://docs.djangoproject.com/en/1.11/ref/csrf/
+var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
+
+
+function csrfSafeMethod(method) {
+ // these HTTP methods do not require CSRF protection
+ return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
+}
+
+$.ajaxSetup({
+ beforeSend: function(xhr, settings) {
+ if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
+ xhr.setRequestHeader("X-CSRFToken", csrftoken);
+ }
+ }
+});
\ No newline at end of file
diff --git a/lab/templates/lab/js/search.js b/lab/templates/lab/js/search.js
index b1cc8d8..eec6f14 100644
--- a/lab/templates/lab/js/search.js
+++ b/lab/templates/lab/js/search.js
@@ -54,8 +54,17 @@ function search(id_search, call) {
// hide if no hit
if (finder > 0) {
row[i].style.display = "";
+ $('#id_search_error').hide();
} else {
row[i].style.display = "none";
}
}
+
+ var count = row.filter(function() {
+ return $(this).css('display') !== 'none';
+ }).length;
+ if (count === 0) {
+ window.scrollTo(0, 0);
+ $('#id_search_error').show();
+ }
}
diff --git a/lab/urls.py b/lab/urls.py
index 2a64ce5..22cf310 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'),
@@ -74,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 e142f3a..a9481a8 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}
@@ -1283,8 +1286,18 @@ def reagents_edit(request, reagent):
def reagents_delete(request):
manipulation = framework.TableManipulation(table=models.Reagents,
table_audit_trail=models.ReagentsAuditTrail)
- response = manipulation.delete_multiple(records=json.loads(request.POST.get('items')),
- user=request.user.username)
+ manipulation_boxing = framework.TableManipulation(table=models.Boxing)
+ # individual loop for deleting reagents to clear boxing list
+ _success_list = list()
+ for item in json.loads(request.POST.get('items')):
+ box = models.Overview.objects.box(unique=item)
+ position = models.Overview.objects.position(unique=item)
+ response = manipulation.delete_at(record=item, user=request.user.username)
+ if box and response:
+ response = manipulation_boxing.clear_boxing(box=box, object='', position=position)
+ _success_list.append(response)
+ response = custom.check_equal(_success_list)
+
"""if response:
manipulation_dynamic = framework.TableManipulation(table=models.DynamicReagents,
table_audit_trail=models.DynamicReagentsAuditTrail)
@@ -1603,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': '---',
+ 'box': '---',
+ 'position': '---'}
+ return JsonResponse(data)
+ # return nothing if no mixed boxes available
+ else:
data = {'response': True,
- 'location': location,
- 'box': box,
- 'position': position}
+ '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
@@ -1686,6 +1739,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):
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,
diff --git a/static/custom.css b/static/custom.css
index 875c9ea..41e66f2 100644
--- a/static/custom.css
+++ b/static/custom.css
@@ -206,4 +206,9 @@ html, body {
.sidebar-active a svg {
color: white;
+}
+
+.search-error {
+ text-align: center;
+ color: red;
}
\ No newline at end of file
diff --git a/turtle/settings/settings.py b/turtle/settings/settings.py
index 15674d4..1ecce0f 100644
--- a/turtle/settings/settings.py
+++ b/turtle/settings/settings.py
@@ -246,7 +246,7 @@ def _require_file(path, file_name):
# security settings of type bool
CSRF_COOKIE_SECURE = custom.value_to_bool(os.environ.get('CSRF_COOKIE_SECURE', 0))
-CSRF_USE_SESSIONS = custom.value_to_bool(os.environ.get('CSRF_USE_SESSIONS', 0))
+CSRF_USE_SESSIONS = custom.value_to_bool(os.environ.get('CSRF_USE_SESSIONS', 1))
SESSION_COOKIE_SECURE = custom.value_to_bool(os.environ.get('SESSION_COOKIE_SECURE', 0))
SECURE_CONTENT_TYPE_NOSNIFF = custom.value_to_bool(os.environ.get('SECURE_CONTENT_TYPE_NOSNIFF', 0))
SECURE_BROWSER_XSS_FILTER = custom.value_to_bool(os.environ.get('SECURE_BROWSER_XSS_FILTER', 0))