From 538541cd4dd815895e0a087212f25a4c91687abd Mon Sep 17 00:00:00 2001 From: RubenPX Date: Mon, 25 Apr 2022 11:49:11 +0200 Subject: [PATCH 01/16] filter in out trades from lots selector --- ereuse_devicehub/static/js/api.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ereuse_devicehub/static/js/api.js b/ereuse_devicehub/static/js/api.js index 190e5d42..8163e293 100644 --- a/ereuse_devicehub/static/js/api.js +++ b/ereuse_devicehub/static/js/api.js @@ -5,7 +5,10 @@ const Api = { */ async get_lots() { const request = await this.doRequest(API_URLS.lots, "GET", null); - if (request != undefined) return request.items; + if (request != undefined) { + request.items = request.items.filter(itm => !itm.trade) // Avoid show outgoing or incomming trades + return request.items; + } throw request; }, From c8dc1b11e225103848b3a786c17843259e0178b8 Mon Sep 17 00:00:00 2001 From: RubenPX Date: Thu, 28 Apr 2022 11:22:53 +0200 Subject: [PATCH 02/16] Add trade type on api options --- ereuse_devicehub/resources/lot/views.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ereuse_devicehub/resources/lot/views.py b/ereuse_devicehub/resources/lot/views.py index dce6af62..b99e622f 100644 --- a/ereuse_devicehub/resources/lot/views.py +++ b/ereuse_devicehub/resources/lot/views.py @@ -29,6 +29,7 @@ class LotView(View): """ format = EnumField(LotFormat, missing=None) search = f.Str(missing=None) + type = f.Str(missing=None) def post(self): l = request.get_json() @@ -88,6 +89,7 @@ class LotView(View): else: query = Lot.query query = self.visibility_filter(query) + query = self.type_filter(query, args) if args['search']: query = query.filter(Lot.name.ilike(args['search'] + '%')) lots = query.paginate(per_page=6 if args['search'] else query.count()) @@ -104,6 +106,21 @@ class LotView(View): Lot.owner_id == g.user.id)) return query + def type_filter(self, query, args): + lot_type = args.get('type') + + # temporary + if lot_type == "temporary": + return query.filter(Lot.trade == None) + + if lot_type == "incoming": + return query.filter(Lot.trade and Trade.user_to == g.user) + + if lot_type == "outgoing": + return query.filter(Lot.trade and Trade.user_from == g.user) + + return query + def query(self, args): query = Lot.query.distinct() return query From a945fc085f05f8ed1c1c9fe135dbf70612bf6747 Mon Sep 17 00:00:00 2001 From: RubenPX Date: Thu, 28 Apr 2022 11:31:24 +0200 Subject: [PATCH 03/16] Query using Api instead of javascript filter --- ereuse_devicehub/static/js/api.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/ereuse_devicehub/static/js/api.js b/ereuse_devicehub/static/js/api.js index 8163e293..552544d2 100644 --- a/ereuse_devicehub/static/js/api.js +++ b/ereuse_devicehub/static/js/api.js @@ -4,11 +4,8 @@ const Api = { * @returns get lots */ async get_lots() { - const request = await this.doRequest(API_URLS.lots, "GET", null); - if (request != undefined) { - request.items = request.items.filter(itm => !itm.trade) // Avoid show outgoing or incomming trades - return request.items; - } + const request = await this.doRequest(`${API_URLS.lots}?type=temporary`, "GET", null); + if (request != undefined) return request.items; throw request; }, From 2ffa606bc356bc966bf12bd9f2b8f648d18170f1 Mon Sep 17 00:00:00 2001 From: RubenPX Date: Thu, 28 Apr 2022 11:53:49 +0200 Subject: [PATCH 04/16] Documentation API --- docs/lots.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/lots.rst b/docs/lots.rst index e3cc83a3..03943e21 100644 --- a/docs/lots.rst +++ b/docs/lots.rst @@ -9,6 +9,12 @@ dags-with-materialized-paths-using-postgres-ltree/>`_ you have a low-level technical implementation of how lots and their relationships are mapped. +Getting lots +************ + +You can get lots list by ``GET /lots/`` +There are one optional filter ``type``, only works with this 3 values ``temporary``, ``incoming`` and ``outgoing`` + Create lots *********** You create a lot by ``POST /lots/`` a `JSON Lot object /devices/?id=&id=``; idem for removing devices. - Sharing lots ************ Sharing a lot means giving certain permissions to users, like reading From 9d9514e68b66f9596e26924a54fa56af079a0eec Mon Sep 17 00:00:00 2001 From: RubenPX Date: Tue, 3 May 2022 12:13:15 +0200 Subject: [PATCH 05/16] Select full list devices --- ereuse_devicehub/static/js/main_inventory.js | 16 ++++++++++++++++ .../templates/inventory/device_list.html | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/ereuse_devicehub/static/js/main_inventory.js b/ereuse_devicehub/static/js/main_inventory.js index 9732b5ef..b7b4387c 100644 --- a/ereuse_devicehub/static/js/main_inventory.js +++ b/ereuse_devicehub/static/js/main_inventory.js @@ -76,6 +76,7 @@ class TableController { */ window.addEventListener("DOMContentLoaded", () => { const btnSelectAll = document.getElementById("SelectAllBTN"); + const alertInfoDevices = document.getElementById("select-devices-info"); function itemListCheckChanged() { const listDevices = TableController.getAllDevicesInCurrentPage() @@ -84,11 +85,20 @@ window.addEventListener("DOMContentLoaded", () => { if (isAllChecked.every(bool => bool == true)) { btnSelectAll.checked = true; btnSelectAll.indeterminate = false; + alertInfoDevices.innerHTML = `Selected devices: ${TableController.getSelectedDevices().length} + ${ + TableController.getAllDevices().length != TableController.getSelectedDevices().length + ? `Select all devices (${TableController.getAllDevices().length})` + : "" + }`; + alertInfoDevices.classList.remove("d-none"); } else if (isAllChecked.every(bool => bool == false)) { btnSelectAll.checked = false; btnSelectAll.indeterminate = false; + alertInfoDevices.classList.add("d-none") } else { btnSelectAll.indeterminate = true; + alertInfoDevices.classList.add("d-none") } } @@ -99,6 +109,12 @@ window.addEventListener("DOMContentLoaded", () => { btnSelectAll.addEventListener("click", event => { const checkedState = event.target.checked; TableController.getAllDevicesInCurrentPage().forEach(ckeckbox => { ckeckbox.checked = checkedState }); + itemListCheckChanged() + }) + + alertInfoDevices.addEventListener("click", () => { + TableController.getAllDevices().forEach(ckeckbox => { ckeckbox.checked = true }); + itemListCheckChanged() }) // https://github.com/fiduswriter/Simple-DataTables/wiki/Events diff --git a/ereuse_devicehub/templates/inventory/device_list.html b/ereuse_devicehub/templates/inventory/device_list.html index 827c3b68..c3b910ae 100644 --- a/ereuse_devicehub/templates/inventory/device_list.html +++ b/ereuse_devicehub/templates/inventory/device_list.html @@ -297,6 +297,10 @@ {% endif %} + +
From 2aab4b49d3bdadc2bb61c91a81d9dea0b9170fc2 Mon Sep 17 00:00:00 2001 From: RubenPX Date: Tue, 3 May 2022 12:52:26 +0200 Subject: [PATCH 06/16] Allow user to cancel all selection --- ereuse_devicehub/static/js/main_inventory.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ereuse_devicehub/static/js/main_inventory.js b/ereuse_devicehub/static/js/main_inventory.js index b7b4387c..a183dd72 100644 --- a/ereuse_devicehub/static/js/main_inventory.js +++ b/ereuse_devicehub/static/js/main_inventory.js @@ -89,7 +89,7 @@ window.addEventListener("DOMContentLoaded", () => { ${ TableController.getAllDevices().length != TableController.getSelectedDevices().length ? `Select all devices (${TableController.getAllDevices().length})` - : "" + : "Cancel selection" }`; alertInfoDevices.classList.remove("d-none"); } else if (isAllChecked.every(bool => bool == false)) { @@ -113,7 +113,8 @@ window.addEventListener("DOMContentLoaded", () => { }) alertInfoDevices.addEventListener("click", () => { - TableController.getAllDevices().forEach(ckeckbox => { ckeckbox.checked = true }); + const checkState = TableController.getAllDevices().length == TableController.getSelectedDevices().length + TableController.getAllDevices().forEach(ckeckbox => { ckeckbox.checked = !checkState }); itemListCheckChanged() }) From 8a6b0cde378e94d1972a260e4215132812c21695 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 4 May 2022 12:23:04 +0200 Subject: [PATCH 07/16] change colors and log --- ereuse_devicehub/static/css/style.css | 37 ++++++-- .../static/img/logo_usody_clock.png | Bin 0 -> 22156 bytes .../static/img/logo_usody_clock.svg | 83 ++++++++++++++++++ .../templates/ereuse_devicehub/base_site.html | 2 +- 4 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 ereuse_devicehub/static/img/logo_usody_clock.png create mode 100644 ereuse_devicehub/static/img/logo_usody_clock.svg diff --git a/ereuse_devicehub/static/css/style.css b/ereuse_devicehub/static/css/style.css index 1fd44f19..c42018ea 100644 --- a/ereuse_devicehub/static/css/style.css +++ b/ereuse_devicehub/static/css/style.css @@ -19,12 +19,12 @@ body { } a { - color: #4154f1; + color: #6c757d ; text-decoration: none; } a:hover { - color: #717ff5; + color: #cc0066; text-decoration: none; } @@ -56,7 +56,7 @@ h1, h2, h3, h4, h5, h6 { font-size: 24px; margin-bottom: 0; font-weight: 600; - color: #012970; + color: #993365; } /*-------------------------------------------------------------- @@ -176,6 +176,31 @@ h1, h2, h3, h4, h5, h6 { opacity: 0; } } + +.btn-primary { + background-color: #993365; + border-color: #993365; + color: #fff; +} + +.btn-primary:hover { + background-color: #cc0066; + border-color: #cc0066; + color: #fff; +} + +.btn-danger { + background-color: #b3b1b1; + border-color: #b3b1b1; + color: #fff; +} + +.btn-danger:hover { + background-color: #645e5f; + border-color: #645e5f; + color: #fff; +} + /* Light Backgrounds */ .bg-primary-light { background-color: #cfe2ff; @@ -370,7 +395,7 @@ h1, h2, h3, h4, h5, h6 { font-size: 32px; padding-left: 10px; cursor: pointer; - color: #012970; + color: #993365; } .header .search-bar { min-width: 360px; @@ -439,7 +464,7 @@ h1, h2, h3, h4, h5, h6 { color: #012970; } .header-nav .nav-profile { - color: #012970; + color: #993365; } .header-nav .nav-profile img { max-height: 36px; @@ -618,7 +643,7 @@ h1, h2, h3, h4, h5, h6 { color: #4154f1; } .sidebar-nav .nav-link.collapsed { - color: #012970; + color: #6c757d; background: #fff; } .sidebar-nav .nav-link.collapsed i { diff --git a/ereuse_devicehub/static/img/logo_usody_clock.png b/ereuse_devicehub/static/img/logo_usody_clock.png new file mode 100644 index 0000000000000000000000000000000000000000..303447653e3cbea54913959e87e7c27cc57088fc GIT binary patch literal 22156 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 MQ(vP{%_ijk0E3ZFod5s; literal 0 HcmV?d00001 diff --git a/ereuse_devicehub/static/img/logo_usody_clock.svg b/ereuse_devicehub/static/img/logo_usody_clock.svg new file mode 100644 index 00000000..4d03247e --- /dev/null +++ b/ereuse_devicehub/static/img/logo_usody_clock.svg @@ -0,0 +1,83 @@ + + + + + + + + Usody + + + + + + + + + + + + diff --git a/ereuse_devicehub/templates/ereuse_devicehub/base_site.html b/ereuse_devicehub/templates/ereuse_devicehub/base_site.html index f10172e5..fe014107 100644 --- a/ereuse_devicehub/templates/ereuse_devicehub/base_site.html +++ b/ereuse_devicehub/templates/ereuse_devicehub/base_site.html @@ -6,7 +6,7 @@

From f2a85af1c268f83a4eaf0cacbb338326ad997981 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 4 May 2022 12:53:22 +0200 Subject: [PATCH 08/16] fix login --- ereuse_devicehub/static/css/style.css | 14 +++++++++++--- .../templates/ereuse_devicehub/user_login.html | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/ereuse_devicehub/static/css/style.css b/ereuse_devicehub/static/css/style.css index c42018ea..6c2cd65e 100644 --- a/ereuse_devicehub/static/css/style.css +++ b/ereuse_devicehub/static/css/style.css @@ -183,7 +183,7 @@ h1, h2, h3, h4, h5, h6 { color: #fff; } -.btn-primary:hover { +.btn-primary:hover, .btn-primary:focus { background-color: #cc0066; border-color: #cc0066; color: #fff; @@ -1028,7 +1028,7 @@ h1, h2, h3, h4, h5, h6 { padding: 12px 15px; } .contact .php-email-form button[type=submit] { - background: #4154f1; + background: #993365; border: 0; padding: 10px 30px; color: #fff; @@ -1036,7 +1036,15 @@ h1, h2, h3, h4, h5, h6 { border-radius: 4px; } .contact .php-email-form button[type=submit]:hover { - background: #5969f3; + background: #993365; +} +button[type=submit] { + background-color: #993365; + border-color: #993365; +} +button[type=submit]:hover { + background-color: #993365; + border-color: #993365; } @-webkit-keyframes animate-loading { 0% { diff --git a/ereuse_devicehub/templates/ereuse_devicehub/user_login.html b/ereuse_devicehub/templates/ereuse_devicehub/user_login.html index b234927c..f010e3d7 100644 --- a/ereuse_devicehub/templates/ereuse_devicehub/user_login.html +++ b/ereuse_devicehub/templates/ereuse_devicehub/user_login.html @@ -13,7 +13,7 @@ From 074fd4e54284765104dca34e9137bc3f5d2c7e8a Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 4 May 2022 17:39:20 +0200 Subject: [PATCH 09/16] fix sidebar --- ereuse_devicehub/static/css/style.css | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ereuse_devicehub/static/css/style.css b/ereuse_devicehub/static/css/style.css index 6c2cd65e..108ff925 100644 --- a/ereuse_devicehub/static/css/style.css +++ b/ereuse_devicehub/static/css/style.css @@ -631,7 +631,7 @@ h1, h2, h3, h4, h5, h6 { align-items: center; font-size: 15px; font-weight: 600; - color: #4154f1; + color: #993365; transition: 0.3; background: #f6f9ff; padding: 10px 15px; @@ -640,7 +640,7 @@ h1, h2, h3, h4, h5, h6 { .sidebar-nav .nav-link i { font-size: 16px; margin-right: 10px; - color: #4154f1; + color: #993365; } .sidebar-nav .nav-link.collapsed { color: #6c757d; @@ -650,11 +650,11 @@ h1, h2, h3, h4, h5, h6 { color: #899bbd; } .sidebar-nav .nav-link:hover { - color: #4154f1; + color: #993365; background: #f6f9ff; } .sidebar-nav .nav-link:hover i { - color: #4154f1; + color: #993365; } .sidebar-nav .nav-link .bi-chevron-down { margin-right: 0; @@ -685,7 +685,7 @@ h1, h2, h3, h4, h5, h6 { border-radius: 50%; } .sidebar-nav .nav-content a:hover, .sidebar-nav .nav-content a.active { - color: #4154f1; + color: #993365; } .sidebar-nav .nav-content a.active i { background-color: #4154f1; From 9a74eabc23e3cf46138683a13251502fbdf9edd3 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 4 May 2022 18:06:19 +0200 Subject: [PATCH 10/16] add color in bordered navs links --- ereuse_devicehub/static/css/style.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ereuse_devicehub/static/css/style.css b/ereuse_devicehub/static/css/style.css index 108ff925..1766a804 100644 --- a/ereuse_devicehub/static/css/style.css +++ b/ereuse_devicehub/static/css/style.css @@ -351,12 +351,12 @@ h1, h2, h3, h4, h5, h6 { color: #2c384e; } .nav-tabs-bordered .nav-link:hover, .nav-tabs-bordered .nav-link:focus { - color: #4154f1; + color: #993365; } .nav-tabs-bordered .nav-link.active { background-color: #fff; - color: #4154f1; - border-bottom: 2px solid #4154f1; + color: #993365; + border-bottom: 2px solid #993365; } /*-------------------------------------------------------------- From 9eda5c0466e0271dab7c9c1e31d5ec4dee24897a Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 5 May 2022 11:31:13 +0200 Subject: [PATCH 11/16] add checks of device.allocated --- ereuse_devicehub/inventory/forms.py | 36 ++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/ereuse_devicehub/inventory/forms.py b/ereuse_devicehub/inventory/forms.py index 08b6eeca..0b88aa95 100644 --- a/ereuse_devicehub/inventory/forms.py +++ b/ereuse_devicehub/inventory/forms.py @@ -593,10 +593,14 @@ class NewActionForm(ActionFormMix): class AllocateForm(ActionFormMix): start_time = DateField('Start time') - end_time = DateField('End time') - final_user_code = StringField('Final user code', [validators.length(max=50)]) - transaction = StringField('Transaction', [validators.length(max=50)]) - end_users = IntegerField('End users') + end_time = DateField('End time', [validators.Optional()]) + final_user_code = StringField( + 'Final user code', [validators.Optional(), validators.length(max=50)] + ) + transaction = StringField( + 'Transaction', [validators.Optional(), validators.length(max=50)] + ) + end_users = IntegerField('End users', [validators.Optional()]) def validate(self, extra_validators=None): is_valid = super().validate(extra_validators) @@ -608,13 +612,29 @@ class AllocateForm(ActionFormMix): end_time = self.end_time.data if start_time and end_time and end_time < start_time: error = ['The action cannot finish before it starts.'] - self.start_time.errors = error self.end_time.errors = error is_valid = False - if not self.end_users.data: - self.end_users.errors = ["You need to specify a number of users"] - is_valid = False + if is_valid and not end_time: + self.end_time.data = self.start_time.data + + if self.type.data == 'Allocate': + txt = "You need deallocate before allocate this device again" + for device in self._devices: + if device.allocated: + self.devices.errors = [txt] + return False + + device.allocated = True + + if self.type.data == 'Deallocate': + txt = "Sorry some of this devices are actually deallocate" + for device in self._devices: + if not device.allocated: + self.devices.errors = [txt] + return False + + device.allocated = False return is_valid From 1023f3fdcae412b0505eee3863842345bba8be89 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 5 May 2022 12:00:02 +0200 Subject: [PATCH 12/16] add public link in model --- ereuse_devicehub/resources/device/models.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ereuse_devicehub/resources/device/models.py b/ereuse_devicehub/resources/device/models.py index 09451277..c08ec3b8 100644 --- a/ereuse_devicehub/resources/device/models.py +++ b/ereuse_devicehub/resources/device/models.py @@ -10,7 +10,7 @@ from typing import Dict, List, Set from boltons import urlutils from citext import CIText from ereuse_utils.naming import HID_CONVERSION_DOC, Naming -from flask import g +from flask import g, request from flask_sqlalchemy import event from more_itertools import unique_everseen from sqlalchemy import BigInteger, Boolean, Column @@ -297,6 +297,11 @@ class Device(Thing): actions.reverse() return actions + @property + def public_link(self) -> str: + host_url = request.host_url.strip('/') + return "{}{}".format(host_url, self.url.to_text()) + @property def url(self) -> urlutils.URL: """The URL where to GET this device.""" From 1c670641f4cdabd29e550f7c271a4579f6e7c008 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 5 May 2022 12:00:40 +0200 Subject: [PATCH 13/16] add web: public link in device details page --- ereuse_devicehub/templates/inventory/device_detail.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ereuse_devicehub/templates/inventory/device_detail.html b/ereuse_devicehub/templates/inventory/device_detail.html index 7a96c4a9..051cde2a 100644 --- a/ereuse_devicehub/templates/inventory/device_detail.html +++ b/ereuse_devicehub/templates/inventory/device_detail.html @@ -26,6 +26,10 @@ + + From 33fd574f18b9f9974baa8c2f375e971412c40fbd Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 5 May 2022 12:31:29 +0200 Subject: [PATCH 14/16] add lots tab in device details --- .../templates/inventory/device_detail.html | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/ereuse_devicehub/templates/inventory/device_detail.html b/ereuse_devicehub/templates/inventory/device_detail.html index 051cde2a..f18c1e0e 100644 --- a/ereuse_devicehub/templates/inventory/device_detail.html +++ b/ereuse_devicehub/templates/inventory/device_detail.html @@ -30,6 +30,10 @@ Web + + @@ -73,6 +77,50 @@
+
+
Incoming Lots
+ +
+ {% for lot in device.lots %} + {% if lot.is_incoming %} + + {% endif %} + {% endfor %} +
+ +
Outgoing Lots
+ +
+ {% for lot in device.lots %} + {% if lot.is_outgoing %} + + {% endif %} + {% endfor %} +
+ +
Temporary Lots
+ +
+ {% for lot in device.lots %} + {% if lot.is_temporary %} + + {% endif %} + {% endfor %} +
+
+
Status Details
From af30453c12afc6643163a2958a502c130fd0e239 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 5 May 2022 13:10:02 +0200 Subject: [PATCH 15/16] fix message of allocate bug --- ereuse_devicehub/inventory/forms.py | 26 ++++++++++++++++++++++---- tests/test_render_2_0.py | 2 +- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/ereuse_devicehub/inventory/forms.py b/ereuse_devicehub/inventory/forms.py index 0b88aa95..d58fcaf9 100644 --- a/ereuse_devicehub/inventory/forms.py +++ b/ereuse_devicehub/inventory/forms.py @@ -603,21 +603,39 @@ class AllocateForm(ActionFormMix): end_users = IntegerField('End users', [validators.Optional()]) def validate(self, extra_validators=None): - is_valid = super().validate(extra_validators) + if not super().validate(extra_validators): + return False if self.type.data not in ['Allocate', 'Deallocate']: return False + if not self.validate_dates(): + return False + + if not self.check_devices(): + return False + + return True + + def validate_dates(self): start_time = self.start_time.data end_time = self.end_time.data + + if not start_time: + self.start_time.errors = ['Not a valid date value.!'] + return False + if start_time and end_time and end_time < start_time: error = ['The action cannot finish before it starts.'] self.end_time.errors = error - is_valid = False + return False - if is_valid and not end_time: + if not end_time: self.end_time.data = self.start_time.data + return True + + def check_devices(self): if self.type.data == 'Allocate': txt = "You need deallocate before allocate this device again" for device in self._devices: @@ -636,7 +654,7 @@ class AllocateForm(ActionFormMix): device.allocated = False - return is_valid + return True class DataWipeDocumentForm(Form): diff --git a/tests/test_render_2_0.py b/tests/test_render_2_0.py index 0df56652..8e9f89af 100644 --- a/tests/test_render_2_0.py +++ b/tests/test_render_2_0.py @@ -668,7 +668,7 @@ def test_action_allocate_error_required(user3: UserClientFlask): body, status = user3.post(uri, data=data) assert status == '200 OK' assert 'Action Allocate error' in body - assert 'You need to specify a number of users!' in body + assert 'Not a valid date value.' in body @pytest.mark.mvp From 3ca729244835daa1815118443e577229ad6520b9 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 5 May 2022 18:36:52 +0200 Subject: [PATCH 16/16] drop download public links --- ereuse_devicehub/inventory/views.py | 14 -------------- .../templates/inventory/device_list.html | 6 ------ tests/test_render_2_0.py | 12 ------------ 3 files changed, 32 deletions(-) diff --git a/ereuse_devicehub/inventory/views.py b/ereuse_devicehub/inventory/views.py index 503632e6..2df8fd69 100644 --- a/ereuse_devicehub/inventory/views.py +++ b/ereuse_devicehub/inventory/views.py @@ -421,7 +421,6 @@ class ExportsView(View): 'metrics': self.metrics, 'devices': self.devices_list, 'certificates': self.erasure, - 'links': self.public_links, } if export_id not in export_ids: @@ -497,19 +496,6 @@ class ExportsView(View): return self.response_csv(data, "actions_export.csv") - def public_links(self): - # get a csv with the publink links of this devices - data = StringIO() - cw = csv.writer(data, delimiter=';', lineterminator="\n", quotechar='"') - cw.writerow(['links']) - host_url = request.host_url - for dev in self.find_devices(): - code = dev.devicehub_id - link = [f"{host_url}devices/{code}"] - cw.writerow(link) - - return self.response_csv(data, "links.csv") - def erasure(self): template = self.build_erasure_certificate() res = flask_weasyprint.render_pdf( diff --git a/ereuse_devicehub/templates/inventory/device_list.html b/ereuse_devicehub/templates/inventory/device_list.html index 0f5dcedb..b95334f8 100644 --- a/ereuse_devicehub/templates/inventory/device_list.html +++ b/ereuse_devicehub/templates/inventory/device_list.html @@ -201,12 +201,6 @@ Metrics Spreadsheet -
  • - - - Public Links - -
  • diff --git a/tests/test_render_2_0.py b/tests/test_render_2_0.py index 8e9f89af..ac41ef09 100644 --- a/tests/test_render_2_0.py +++ b/tests/test_render_2_0.py @@ -243,18 +243,6 @@ def test_export_metrics(user3: UserClientFlask): assert body == '' -@pytest.mark.mvp -@pytest.mark.usefixtures(conftest.app_context.__name__) -def test_export_links(user3: UserClientFlask): - snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json') - uri = "/inventory/export/links/?ids={id}".format(id=snap.device.devicehub_id) - - body, status = user3.get(uri) - assert status == '200 OK' - body = body.split("\n") - assert ['links', 'http://localhost/devices/O48N2', ''] == body - - @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_export_certificates(user3: UserClientFlask):