diff --git a/ereuse_devicehub/inventory/forms.py b/ereuse_devicehub/inventory/forms.py index f0adcb8c..e907e72b 100644 --- a/ereuse_devicehub/inventory/forms.py +++ b/ereuse_devicehub/inventory/forms.py @@ -89,6 +89,26 @@ MONITORS = ["ComputerMonitor", "Monitor", "TelevisionSet", "Projector"] MOBILE = ["Mobile", "Tablet", "Smartphone", "Cellphone"] +class AdvancedSearchForm(FlaskForm): + q = StringField('Search', [validators.length(min=1)]) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + dhids = [x.strip() for x in self.q.data.split(' ')] + if ',' in self.q.data: + dhids = [x.strip() for x in self.q.data.split(',')] + if ';' in self.q.data: + dhids = [x.strip() for x in self.q.data.split(';')] + + self.devices = [] + if dhids: + self.search(dhids) + + def search(self, dhids): + self.devices = Device.query.filter(Device.devicehub_id.in_(dhids)) + + class FilterForm(FlaskForm): filter = SelectField( '', choices=DEVICES, default="All Computers", render_kw={'class': "form-select"} diff --git a/ereuse_devicehub/inventory/views.py b/ereuse_devicehub/inventory/views.py index 344f00ef..51cf36b7 100644 --- a/ereuse_devicehub/inventory/views.py +++ b/ereuse_devicehub/inventory/views.py @@ -13,6 +13,7 @@ from werkzeug.exceptions import NotFound from ereuse_devicehub import messages from ereuse_devicehub.db import db from ereuse_devicehub.inventory.forms import ( + AdvancedSearchForm, AllocateForm, DataWipeForm, EditTransferForm, @@ -101,6 +102,19 @@ class DeviceListView(DeviceListMixin): return flask.render_template(self.template_name, **self.context) +class AdvancedSearchView(DeviceListMixin): + methods = ['GET', 'POST'] + template_name = 'inventory/search.html' + title = "Advanced Search" + + def dispatch_request(self): + query = request.args.get('q', '') + self.get_context(None) + form = AdvancedSearchForm(q=query) + self.context.update({'devices': form.devices, 'advanced_form': form}) + return flask.render_template(self.template_name, **self.context) + + class DeviceDetailView(GenericMixin): decorators = [login_required] template_name = 'inventory/device_detail.html' @@ -630,6 +644,9 @@ devices.add_url_rule( view_func=NewTradeDocumentView.as_view('trade_document_add'), ) devices.add_url_rule('/device/', view_func=DeviceListView.as_view('devicelist')) +devices.add_url_rule( + '/search/', view_func=AdvancedSearchView.as_view('advanced_search') +) devices.add_url_rule( '/device//', view_func=DeviceDetailView.as_view('device_details') ) diff --git a/ereuse_devicehub/static/css/devicehub.css b/ereuse_devicehub/static/css/devicehub.css index e6ae1893..8ff63bbb 100644 --- a/ereuse_devicehub/static/css/devicehub.css +++ b/ereuse_devicehub/static/css/devicehub.css @@ -15,7 +15,7 @@ margin: 0; padding: 0; min-width: max-content; - max-height: 380px; + max-height: 330px; overflow-y: auto; } diff --git a/ereuse_devicehub/static/js/main_inventory.build.js b/ereuse_devicehub/static/js/main_inventory.build.js index 34dd7bfa..4ee7afe9 100644 --- a/ereuse_devicehub/static/js/main_inventory.build.js +++ b/ereuse_devicehub/static/js/main_inventory.build.js @@ -348,10 +348,12 @@ class lotsSearcher { const lots = this.getListLots(); for (let i = 0; i < lots.length; i++) { + const lot = lots[i]; + if (lot.innerText.toLowerCase().includes(inputSearch.toLowerCase())) { - lot.parentElement.style.display = ""; + lot.style.display = ""; } else { - lot.parentElement.style.display = "none"; + lot.style.display = "none"; } } } @@ -363,7 +365,7 @@ _defineProperty(lotsSearcher, "lots", []); _defineProperty(lotsSearcher, "lotsSearchElement", null); _defineProperty(lotsSearcher, "getListLots", () => { - let lotsList = document.getElementById("LotsSelector"); + const lotsList = document.getElementById("LotsSelector"); if (lotsList) { // Apply filter to get only labels diff --git a/ereuse_devicehub/static/js/main_inventory.js b/ereuse_devicehub/static/js/main_inventory.js index bfd88858..f59bf588 100644 --- a/ereuse_devicehub/static/js/main_inventory.js +++ b/ereuse_devicehub/static/js/main_inventory.js @@ -345,10 +345,11 @@ class lotsSearcher { static doSearch(inputSearch) { const lots = this.getListLots(); for (let i = 0; i < lots.length; i++) { + const lot = lots[i] if (lot.innerText.toLowerCase().includes(inputSearch.toLowerCase())) { - lot.parentElement.style.display = ""; + lot.style.display = ""; } else { - lot.parentElement.style.display = "none"; + lot.style.display = "none"; } } } diff --git a/ereuse_devicehub/templates/ereuse_devicehub/base_site.html b/ereuse_devicehub/templates/ereuse_devicehub/base_site.html index cf60777a..f86e31ae 100644 --- a/ereuse_devicehub/templates/ereuse_devicehub/base_site.html +++ b/ereuse_devicehub/templates/ereuse_devicehub/base_site.html @@ -26,6 +26,11 @@ style="float: right;">DHID + diff --git a/ereuse_devicehub/templates/inventory/search.html b/ereuse_devicehub/templates/inventory/search.html new file mode 100644 index 00000000..d996a1f3 --- /dev/null +++ b/ereuse_devicehub/templates/inventory/search.html @@ -0,0 +1,412 @@ +{% extends "ereuse_devicehub/base_site.html" %} +{% block main %} + +
+

Inventory

+ +
+ +
+
+ +
+ +
+
+
+
+ {% for f in advanced_form %} + {{ f }} + {% endfor %} + +
+
+
+
+ {% if devices %} +
+
+ +
+
+ + + + + + + + + + + + + {% if lot and not lot.is_temporary %} + + {% endif %} + + + +
+ + + + + + + + + + + + + + + + + {% for dev in devices %} + + + + + + + + + + + + + {% endfor %} + +
SelectTitleDHIDUnique IdentifiersLifecycle StatusAllocated StatusPhysical StatusUpdated inRegistered in
+ + + + {% if dev.get_type_logo() %} + + {% endif %} + {{ dev.verbose_name }} + + {% if dev.lots | length > 0 %} +
+ {% for lot in dev.lots %} + {{ lot.name }} + {% endfor %} +
+ {% endif %} +
+ + {{ dev.devicehub_id }} + + + {% for t in dev.tags | sort(attribute="id") %} + {{ t.id }} + {% if not loop.last %},{% endif %} + {% endfor %} + {% if dev.status %}{{ dev.status.type }}{% endif %}{% if dev.allocated_status %}{{ dev.allocated_status.type }}{% endif %}{% if dev.physical_status %}{{ dev.physical_status.type }}{% endif %}{{ dev.updated.strftime('%Y-%m-%d %H:%M:%S')}}{{ dev.created.strftime('%Y-%m-%d %H:%M:%S')}} + + + +
+ +
+
+ {% if lot and not lot.is_temporary %} +
+
Documents
+ + + + + + + + + {% for doc in lot.trade.documents %} + + + + + {% endfor %} + +
FileUploaded on
+ {% if doc.url %} + {{ doc.file_name}} + {% else %} + {{ doc.file_name}} + {% endif %} + + {{ doc.created.strftime('%H:%M %d-%m-%Y')}} +
+
+ {% endif %} + +
+ {% endif %} +
+
+
+ +
+ +
+ +
+{% include "inventory/addDevicestag.html" %} +{% include "inventory/lot_delete_modal.html" %} +{% include "inventory/actions.html" %} +{% include "inventory/allocate.html" %} +{% include "inventory/data_wipe.html" %} +{% include "inventory/trade.html" %} +{% include "inventory/alert_export_error.html" %} +{% include "inventory/alert_unlink_tag_error.html" %} +{% include "inventory/alert_lots_changes.html" %} + + + + +{% endblock main %} diff --git a/requirements.txt b/requirements.txt index a6a2e138..a72507d0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -42,6 +42,6 @@ sortedcontainers==2.1.0 tqdm==4.32.2 python-decouple==3.3 python-dotenv==0.14.0 -pyjwt==2.0.0a1 +pyjwt==2.4.0 pint==0.9 py-dmidecode==0.1.0 diff --git a/tests/test_basic.py b/tests/test_basic.py index ffa0f3e9..e933dea7 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -43,6 +43,7 @@ def test_api_docs(client: Client): '/documents/erasures/', '/documents/internalstats/', '/documents/lots/', + '/inventory/search/', '/documents/stamps/', '/documents/static/{filename}', '/documents/stock/',