From 2605d938e51d483ee2f0a045549c4de932ba942b Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Thu, 23 Jun 2022 15:51:01 +0200 Subject: [PATCH 01/23] add placeholder model --- ereuse_devicehub/resources/device/models.py | 37 +++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/ereuse_devicehub/resources/device/models.py b/ereuse_devicehub/resources/device/models.py index c42d9bec..fd171bb9 100644 --- a/ereuse_devicehub/resources/device/models.py +++ b/ereuse_devicehub/resources/device/models.py @@ -790,6 +790,43 @@ class DisplayMixin: return v +class Placeholder(Thing): + id = Column(BigInteger, Sequence('placeholder_seq'), primary_key=True) + phid = Column(Unicode(), check_lower('phid'), unique=False) + pallet = Column(BigInteger, nullable=True) + pallet.comment = "used for identification where from where is this placeholders" + info = db.Column(CIText()) + info.comment = "more info of placeholders" + id_device_supplier = db.Column(CIText()) + id_device_supplier.comment = ( + "Identification used for one supplier of one placeholders" + ) + + placeholder_id = db.Column( + BigInteger, + db.ForeignKey(Device.id), + nullable=False, + ) + placeholder = db.relationship( + Device, + backref=backref('placeholder', lazy=True), + primaryjoin=placeholder_id == Device.id, + ) + placeholder_id.comment = "datas of the placeholder" + + device_id = db.Column( + BigInteger, + db.ForeignKey(Device.id), + nullable=True, + ) + device = db.relationship( + Device, + backref=backref('binding', lazy=True), + primaryjoin=placeholder_id == Device.id, + ) + device_id.comment = "device with snapshots than is linked to the placeholder" + + class Computer(Device): """A chassis with components inside that can be processed automatically with Workbench Computer. From 12d55c2d24e1c8f1f4129bd7472da48f8ca433bf Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 28 Jun 2022 17:38:05 +0200 Subject: [PATCH 02/23] add models --- .../versions/aeca9fb50cc6_add_placeholder.py | 60 +++++++++++++++++++ ereuse_devicehub/resources/device/models.py | 23 ++++--- 2 files changed, 71 insertions(+), 12 deletions(-) create mode 100644 ereuse_devicehub/migrations/versions/aeca9fb50cc6_add_placeholder.py diff --git a/ereuse_devicehub/migrations/versions/aeca9fb50cc6_add_placeholder.py b/ereuse_devicehub/migrations/versions/aeca9fb50cc6_add_placeholder.py new file mode 100644 index 00000000..0eef82bd --- /dev/null +++ b/ereuse_devicehub/migrations/versions/aeca9fb50cc6_add_placeholder.py @@ -0,0 +1,60 @@ +"""add placeholder + +Revision ID: aeca9fb50cc6 +Revises: 8d4fe4b497b3 +Create Date: 2022-06-27 13:09:30.497678 + +""" +import citext +import sqlalchemy as sa +from alembic import context, op + +# revision identifiers, used by Alembic. +revision = 'aeca9fb50cc6' +down_revision = '8d4fe4b497b3' +branch_labels = None +depends_on = None + + +def get_inv(): + INV = context.get_x_argument(as_dictionary=True).get('inventory') + if not INV: + raise ValueError("Inventory value is not specified") + return INV + + +def upgrade(): + # creating placeholder table + # con = op.get_bind() + # sql = f"CREATE SEQUENCE {get_inv()}.placeholder_id_seq START 1;" + # con.execute(sql) + + op.create_table( + 'placeholder', + sa.Column( + 'updated', + sa.TIMESTAMP(timezone=True), + server_default=sa.text('CURRENT_TIMESTAMP'), + nullable=False, + ), + sa.Column( + 'created', + sa.TIMESTAMP(timezone=True), + server_default=sa.text('CURRENT_TIMESTAMP'), + nullable=False, + ), + sa.Column('id', sa.BigInteger(), nullable=False), + sa.Column('id_device_supplier', sa.Unicode(), nullable=True), + sa.Column('pallet', sa.Unicode(), nullable=True), + sa.Column('info', citext.CIText(), nullable=True), + sa.Column('device_id', sa.BigInteger(), nullable=False), + sa.Column('binding_id', sa.BigInteger(), nullable=True), + sa.ForeignKeyConstraint(['device_id'], [f'{get_inv()}.device.id']), + sa.ForeignKeyConstraint(['binding_id'], [f'{get_inv()}.device.id']), + sa.PrimaryKeyConstraint('id'), + schema=f'{get_inv()}', + ) + + +def downgrade(): + op.drop_table('placeholder', schema=f'{get_inv()}') diff --git a/ereuse_devicehub/resources/device/models.py b/ereuse_devicehub/resources/device/models.py index 003e0f3d..30c16c5b 100644 --- a/ereuse_devicehub/resources/device/models.py +++ b/ereuse_devicehub/resources/device/models.py @@ -793,8 +793,7 @@ class DisplayMixin: class Placeholder(Thing): id = Column(BigInteger, Sequence('placeholder_seq'), primary_key=True) - phid = Column(Unicode(), check_lower('phid'), unique=False) - pallet = Column(BigInteger, nullable=True) + pallet = Column(Unicode(), nullable=True) pallet.comment = "used for identification where from where is this placeholders" info = db.Column(CIText()) info.comment = "more info of placeholders" @@ -803,29 +802,29 @@ class Placeholder(Thing): "Identification used for one supplier of one placeholders" ) - placeholder_id = db.Column( + device_id = db.Column( BigInteger, db.ForeignKey(Device.id), nullable=False, ) - placeholder = db.relationship( + device = db.relationship( Device, - backref=backref('placeholder', lazy=True), - primaryjoin=placeholder_id == Device.id, + backref=backref('placeholder', lazy=True, uselist=False), + primaryjoin=device_id == Device.id, ) - placeholder_id.comment = "datas of the placeholder" + device_id.comment = "datas of the placeholder" - device_id = db.Column( + binding_id = db.Column( BigInteger, db.ForeignKey(Device.id), nullable=True, ) - device = db.relationship( + binding = db.relationship( Device, - backref=backref('binding', lazy=True), - primaryjoin=placeholder_id == Device.id, + backref=backref('binding', lazy=True, uselist=False), + primaryjoin=binding_id == Device.id, ) - device_id.comment = "device with snapshots than is linked to the placeholder" + binding_id.comment = "device with snapshots than is linked to the placeholder" class Computer(Device): From d2924dfafe131197c979ead4f5a2c5d7cb97af82 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 28 Jun 2022 17:38:31 +0200 Subject: [PATCH 03/23] add fields in template --- .../templates/inventory/device_create.html | 78 +++++++++++++++---- 1 file changed, 65 insertions(+), 13 deletions(-) diff --git a/ereuse_devicehub/templates/inventory/device_create.html b/ereuse_devicehub/templates/inventory/device_create.html index 1ad9a083..1243b41d 100644 --- a/ereuse_devicehub/templates/inventory/device_create.html +++ b/ereuse_devicehub/templates/inventory/device_create.html @@ -70,12 +70,64 @@
- - {{ form.label(class_="form-control") }} - Label that you want link to this device - {% if form.label.errors %} + + {{ form.amount(class_="form-control") }} + Number of devices you want to create of this type + {% if form.amount.errors %}

- {% for error in form.label.errors %} + {% for error in form.amount.errors %} + {{ error }}
+ {% endfor %} +

+ {% endif %} +
+ +
+ + {{ form.phid(class_="form-control") }} + Label that you want link to this device + {% if form.phid.errors %} +

+ {% for error in form.phid.errors %} + {{ error }}
+ {% endfor %} +

+ {% endif %} +
+ +
+ + {{ form.id_device_supplier(class_="form-control") }} + Identity of device for the Supplier + {% if form.id_device_supplier.errors %} +

+ {% for error in form.id_device_supplier.errors %} + {{ error }}
+ {% endfor %} +

+ {% endif %} +
+ +
+ + {{ form.pallet(class_="form-control") }} + Identity of pallet + {% if form.pallet.errors %} +

+ {% for error in form.pallet.errors %} + {{ error }}
+ {% endfor %} +

+ {% endif %} +
+ +
+ + {{ form.info(class_="form-control") }} + Info extra + {% if form.info.errors %} +

+ {% for error in form.info.errors %} {{ error }}
{% endfor %}

@@ -83,7 +135,7 @@
- + {{ form.serial_number(class_="form-control") }} Serial number of this device {% if form.serial_number.errors %} @@ -96,7 +148,7 @@
- + {{ form.model(class_="form-control") }} Name of model {% if form.model.errors %} @@ -109,7 +161,7 @@
- + {{ form.manufacturer(class_="form-control") }} Name of manufacturer {% if form.manufacturer.errors %} @@ -199,7 +251,7 @@
- + {{ form.generation(class_="form-control") }} The generation of the device. {% if form.generation.errors %} @@ -225,7 +277,7 @@
- + {{ form.weight(class_="form-control") }} The weight of the device in Kg. {% if form.weight.errors %} @@ -238,7 +290,7 @@
- + {{ form.width(class_="form-control") }} The width of the device in meters. {% if form.width.errors %} @@ -251,7 +303,7 @@
- + {{ form.height(class_="form-control") }} The height of the device in meters. {% if form.height.errors %} @@ -264,7 +316,7 @@
- + {{ form.depth(class_="form-control") }} The depth of the device in meters. {% if form.depth.errors %} From c6e5d71e156a2a4f919dfd2f03d6a7404b8ff9cc Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 28 Jun 2022 17:39:08 +0200 Subject: [PATCH 04/23] add placeholder in form --- ereuse_devicehub/inventory/forms.py | 46 ++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/ereuse_devicehub/inventory/forms.py b/ereuse_devicehub/inventory/forms.py index f338e102..cfe7fee4 100644 --- a/ereuse_devicehub/inventory/forms.py +++ b/ereuse_devicehub/inventory/forms.py @@ -46,6 +46,7 @@ from ereuse_devicehub.resources.device.models import ( Keyboard, MemoryCardReader, Mouse, + Placeholder, Smartphone, Tablet, ) @@ -300,19 +301,27 @@ class UploadSnapshotForm(SnapshotMixin, FlaskForm): class NewDeviceForm(FlaskForm): type = StringField('Type', [validators.DataRequired()]) - label = StringField('Label') - serial_number = StringField('Seria Number', [validators.DataRequired()]) - model = StringField('Model', [validators.DataRequired()]) - manufacturer = StringField('Manufacturer', [validators.DataRequired()]) + amount = IntegerField( + 'Amount', + [validators.DataRequired(), validators.NumberRange(min=1, max=100)], + default=1, + ) + id_device_supplier = StringField('Id Supplier', [validators.Optional()]) + phid = StringField('Placeholder Hardware identity (Phid)', [validators.Optional()]) + pallet = StringField('Identity of pallet', [validators.Optional()]) + info = TextAreaField('Info', [validators.Optional()]) + serial_number = StringField('Seria Number', [validators.Optional()]) + model = StringField('Model', [validators.Optional()]) + manufacturer = StringField('Manufacturer', [validators.Optional()]) appearance = StringField('Appearance', [validators.Optional()]) functionality = StringField('Functionality', [validators.Optional()]) brand = StringField('Brand') generation = IntegerField('Generation') version = StringField('Version') - weight = FloatField('Weight', [validators.DataRequired()]) - width = FloatField('Width', [validators.DataRequired()]) - height = FloatField('Height', [validators.DataRequired()]) - depth = FloatField('Depth', [validators.DataRequired()]) + weight = FloatField('Weight', [validators.Optional()]) + width = FloatField('Width', [validators.Optional()]) + height = FloatField('Height', [validators.Optional()]) + depth = FloatField('Depth', [validators.Optional()]) variant = StringField('Variant', [validators.Optional()]) sku = StringField('SKU', [validators.Optional()]) image = StringField('Image', [validators.Optional(), validators.URL()]) @@ -451,6 +460,17 @@ class NewDeviceForm(FlaskForm): snapshot_json['device'].imei = self.imei.data snapshot_json['device'].meid = self.meid.data + snapshot_json['device'].placeholder = self.get_placeholder() + + _hid = self.phid.data + if not _hid: + _hid = Placeholder.query.order_by(Placeholder.id.desc()).first() + if not _hid: + _hid = '1' + _hid = str(_hid.id + 1) + + snapshot_json['device'].hid = _hid + snapshot = upload_form.build(snapshot_json) move_json(self.tmp_snapshots, path_snapshot, g.user.email) @@ -462,6 +482,16 @@ class NewDeviceForm(FlaskForm): db.session.commit() return snapshot + def get_placeholder(self): + self.placeholder = Placeholder( + **{ + 'id_device_supplier': self.id_device_supplier.data, + 'info': self.info.data, + 'pallet': self.pallet.data, + } + ) + return self.placeholder + class TagDeviceForm(FlaskForm): tag = SelectField('Tag', choices=[]) From 16daba1fa557261acd0a6e27787db5f8701e8e1a Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 28 Jun 2022 17:40:00 +0200 Subject: [PATCH 05/23] add placeholder in sync function --- ereuse_devicehub/resources/device/sync.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/ereuse_devicehub/resources/device/sync.py b/ereuse_devicehub/resources/device/sync.py index 952e935b..dedaf499 100644 --- a/ereuse_devicehub/resources/device/sync.py +++ b/ereuse_devicehub/resources/device/sync.py @@ -18,6 +18,7 @@ from ereuse_devicehub.resources.device.models import ( Computer, DataStorage, Device, + Placeholder, ) from ereuse_devicehub.resources.tag.model import Tag @@ -195,6 +196,7 @@ class Sync: db_device = Device.query.filter_by( hid=device.hid, owner_id=g.user.id, active=True ).one() + if db_device and db_device.allocated: raise ResourceNotFound('device is actually allocated {}'.format(device)) @@ -226,6 +228,7 @@ class Sync: device.physical_properties, ) db_device = sample_tag.device + if db_device: # Device from hid or tags self.merge(device, db_device) else: # Device is new and tags are not linked to a device @@ -261,6 +264,9 @@ class Sync: if db_device.owner_id != g.user.id: return + if device.placeholder and not db_device.placeholder: + return + for field_name, value in device.physical_properties.items(): if value is not None: setattr(db_device, field_name, value) @@ -272,6 +278,23 @@ class Sync: if hasattr(device, 'system_uuid') and device.system_uuid: db_device.system_uuid = device.system_uuid + if device.placeholder and db_device.placeholder: + db_device.placeholder.pallet = device.placeholder.pallet + db_device.placeholder.info = device.placeholder.info + db_device.placeholder.id_device_supplier = ( + device.placeholder.id_device_supplier + ) + db_device.sku = device.sku + db_device.image = device.image + db_device.brand = device.brand + db_device.generation = device.generation + db_device.variant = device.variant + db_device.version = device.version + db_device.width = device.width + db_device.height = device.height + db_device.depth = device.depth + db_device.weight = device.weight + @staticmethod def add_remove(device: Computer, components: Set[Component]) -> OrderedSet: """Generates the Add and Remove actions (but doesn't add them to From 01e1d6e0b7882d058e9b9dc7586bb068e2a79155 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 29 Jun 2022 11:51:03 +0200 Subject: [PATCH 06/23] add computers as placeholders --- ereuse_devicehub/inventory/forms.py | 22 ++++++++++++++++--- .../templates/inventory/device_create.html | 8 +++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/ereuse_devicehub/inventory/forms.py b/ereuse_devicehub/inventory/forms.py index cfe7fee4..2857f34a 100644 --- a/ereuse_devicehub/inventory/forms.py +++ b/ereuse_devicehub/inventory/forms.py @@ -42,11 +42,14 @@ from ereuse_devicehub.resources.device.models import ( SAI, Cellphone, ComputerMonitor, + Desktop, Device, Keyboard, + Laptop, MemoryCardReader, Mouse, Placeholder, + Server, Smartphone, Tablet, ) @@ -333,6 +336,9 @@ class NewDeviceForm(FlaskForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.devices = { + "Laptop": Laptop, + "Desktop": Desktop, + "Server": Server, "Smartphone": Smartphone, "Tablet": Tablet, "Cellphone": Cellphone, @@ -397,6 +403,15 @@ class NewDeviceForm(FlaskForm): self.meid.errors = error is_valid = False + if self.phid.data: + dev = Device.query.filter_by( + hid=self.phid.data, owner=g.user, active=True + ).first() + if dev and not dev.placeholder: + msg = "Sorry, exist one snapshot device with this HID" + self.phid.errors = [msg] + is_valid = False + if not is_valid: return False @@ -465,11 +480,12 @@ class NewDeviceForm(FlaskForm): _hid = self.phid.data if not _hid: _hid = Placeholder.query.order_by(Placeholder.id.desc()).first() - if not _hid: + if _hid: + _hid = str(_hid.id + 1) + else: _hid = '1' - _hid = str(_hid.id + 1) - snapshot_json['device'].hid = _hid + snapshot_json['device'].hid = _hid.lower() snapshot = upload_form.build(snapshot_json) diff --git a/ereuse_devicehub/templates/inventory/device_create.html b/ereuse_devicehub/templates/inventory/device_create.html index 1243b41d..c36e2e79 100644 --- a/ereuse_devicehub/templates/inventory/device_create.html +++ b/ereuse_devicehub/templates/inventory/device_create.html @@ -36,6 +36,14 @@ + + + + + + + + + + + + + + + + + + + + + + Type of devices + {% if form.type.errors %} +

+ {% for error in form.type.errors %} + {{ error }}
+ {% endfor %} +

+ {% endif %} +
+ +
+ +
+ {{ form.placeholder_file }} +
+
+ +
+ {% if lot_id %} + Cancel + {% else %} + Cancel + {% endif %} + +
+ + + + + + + + +
+
+ + +{% endblock main %} diff --git a/requirements.txt b/requirements.txt index a72507d0..f3bed41b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -45,3 +45,7 @@ python-dotenv==0.14.0 pyjwt==2.4.0 pint==0.9 py-dmidecode==0.1.0 +pandas==1.3.5 +numpy==1.21.6 +odfpy==1.4.1 +xlrd==2.0.1 diff --git a/tests/files/placeholder_test.xls b/tests/files/placeholder_test.xls new file mode 100644 index 0000000000000000000000000000000000000000..89db957ff3d9c804bb754fb5ec4ba79860f0791b GIT binary patch literal 5632 zcmeHLU2IfU5T3j3mRm~cw*094c`dROD8{xXq6WKA{+d{5X#I(j=YW?~)n$n!96?kB;xcH^TlI@a2osuJ1@$z9QBBoGXIXw&W zMLF^_$;|1W^1w{K;`c)TGk-IWR{@LnKXX0z|9oH_Z~?F$xDeO?#1SBkz$V}#;9}qs z;8Nfnz&n9=0ha-n16Kf70#^ZPgXPz3uSWY`pAP%=)U4)O|C9%6O{`)~P|nwX&6KaX ze*zKlQS}9li=V*TCnJoFE;|vm{w>(5HrNJZa~wgs87)K3fV)sWRPN_mZr137P(-Xv zsCXlpG$LyINiCbTT!_+;&$Q2v%4tJfeg*m5tEc=;5Hz+R|HZ|YgcN;++-!YaRlPvY z@qFFsB%PFHsg1}Fo7cdCk^>oo@Rp1?$z1UWSNM$gBb=4sY2|Kx-1j;7r5G+ za@0V=H^;Go#Ei(Qt4#4o`6g1oiIi^sj;w!O-qd4&M*#-y-5-zP*4X35oCL&r?BwX6 z9Zip>oD?MboK)OSSi47u2b>i2bjPfi^L#w&Sbd`-BZ;_^!fmtHP9&T(#_CQEx~S~7 z_6}4}*l|*QZt@6@r~dwal6dcF48 zpyim#E|%4dv2a(oxj#Pa9I|#hFIaosVLREn+f5DIiRN%ydw9zdSubm666>89iYIL= z+0DdTZbAFl#!F)xFJ(K|6|~PHK58*VLw{q^YP1^p{`uOF$1%1k zFRKs5c%zO-JRrd1-isaYSzrbj720MX!4VBY9totiKY2Iw;^18sL_ErXnOk3LzN1cTA(1RQM?Ewe-O+C0ZJ~-$dn_i!aOE|+*T5(UW!%a-bm}Uq% z`SS61e!aG5f9SopOxdvZlOO2pmw@ygzteCoZUjh&JO<Q)ynWy?J#%m(1BiWTYbicH&l=3;`%mpJioxj&oX+>WkBDd z2R&v5WfQzbe_?Ow9w@i#d1NYrAzjcsj2PJi8xDBP3QFqde7OGz<#fFpLfyWg0lf)v z%IP+(%zWtgG<)T=LPgAbK=Z9e2uh!fV*bOp_44iCjeh|Kbxp`g<2LMqQyN-){oF(4 t&c7M5@B2drWg9d^)dolZfvC)$O@CwlYoI&r>z|FPQvVmezyE9fe*n6LPCftt literal 0 HcmV?d00001 diff --git a/tests/test_render_2_0.py b/tests/test_render_2_0.py index 9982890d..a8790cb3 100644 --- a/tests/test_render_2_0.py +++ b/tests/test_render_2_0.py @@ -453,7 +453,8 @@ def test_add_monitor(user3: UserClientFlask): dev = Device.query.one() assert dev.type == 'Monitor' assert dev.placeholder.id_device_supplier == "b2" - assert dev.hid == '1' + assert dev.hid == 'monitor-samsung-lc27t55-aaaab' + assert dev.placeholder.phid == '1' @pytest.mark.mvp @@ -483,7 +484,8 @@ def test_update_monitor(user3: UserClientFlask): dev = Device.query.one() assert dev.type == 'Monitor' assert dev.placeholder.id_device_supplier == "b2" - assert dev.hid == '1' + assert dev.hid == 'monitor-samsung-lc27t55-aaaab' + assert dev.placeholder.phid == '1' assert dev.model == 'lc27t55' assert dev.depth == 0.1 assert dev.placeholder.pallet == "l34" @@ -508,7 +510,8 @@ def test_update_monitor(user3: UserClientFlask): dev = Device.query.one() assert dev.type == 'Monitor' assert dev.placeholder.id_device_supplier == "b2" - assert dev.hid == '1' + assert dev.hid == 'monitor-samsung-lc27t55-aaaab' + assert dev.placeholder.phid == '1' assert dev.model == 'lc27t55' assert dev.depth == 0.1 assert dev.placeholder.pallet == "l34" @@ -542,7 +545,8 @@ def test_add_2_monitor(user3: UserClientFlask): dev = Device.query.one() assert dev.type == 'Monitor' assert dev.placeholder.id_device_supplier == "b1" - assert dev.hid == 'aab' + assert dev.hid == 'monitor-samsung-lc27t55-aaaab' + assert dev.placeholder.phid == 'AAB' assert dev.model == 'lc27t55' assert dev.placeholder.pallet == "l34" @@ -565,7 +569,8 @@ def test_add_2_monitor(user3: UserClientFlask): dev = Device.query.all()[-1] assert dev.type == 'Monitor' assert dev.placeholder.id_device_supplier == "b2" - assert dev.hid == '2' + assert dev.hid == 'monitor-samsung-lcd_43_b-aaaab' + assert dev.placeholder.phid == '2' assert dev.model == 'lcd 43 b' assert dev.placeholder.pallet == "l20" @@ -596,7 +601,8 @@ def test_add_laptop(user3: UserClientFlask): dev = Device.query.one() assert dev.type == 'Laptop' assert dev.placeholder.id_device_supplier == "b2" - assert dev.hid == '1' + assert dev.hid == 'laptop-samsung-lc27t55-aaaab' + assert dev.placeholder.phid == '1' @pytest.mark.mvp @@ -628,21 +634,19 @@ def test_add_with_ammount_laptops(user3: UserClientFlask): for dev in Device.query.all(): assert dev.type == 'Laptop' assert dev.placeholder.id_device_supplier is None - assert dev.hid in [str(x) for x in range(1, num + 1)] + assert dev.hid is None + assert dev.placeholder.phid in [str(x) for x in range(1, num + 1)] assert Device.query.count() == num @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_add_laptop_duplicate(user3: UserClientFlask): - file_name = 'real-eee-1001pxd.snapshot.12.json' - create_device(user3, file_name) uri = '/inventory/device/add/' body, status = user3.get(uri) assert status == '200 OK' assert "New Device" in body - assert Device.query.count() == 10 data = { 'csrf_token': generate_csrf(), @@ -658,8 +662,10 @@ def test_add_laptop_duplicate(user3: UserClientFlask): } body, status = user3.post(uri, data=data) assert status == '200 OK' + assert Device.query.count() == 1 + body, status = user3.post(uri, data=data) assert 'Sorry, exist one snapshot device with this HID' in body - assert Device.query.count() == 10 + assert Device.query.count() == 1 @pytest.mark.mvp @@ -1609,3 +1615,29 @@ def test_export_snapshot_json(user3: UserClientFlask): body, status = user3.get(uri) assert status == '200 OK' assert body == snapshot + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_add_placeholder_excel(user3: UserClientFlask): + + uri = '/inventory/upload-placeholder/' + body, status = user3.get(uri) + assert status == '200 OK' + assert "Upload Placeholder" in body + + file_path = Path(__file__).parent.joinpath('files').joinpath('placeholder_test.xls') + with open(file_path, 'rb') as excel: + data = { + 'csrf_token': generate_csrf(), + 'type': "Laptop", + 'placeholder_file': excel, + } + user3.post(uri, data=data, content_type="multipart/form-data") + assert Device.query.count() == 1 + dev = Device.query.first() + assert dev.hid == 'laptop-sony-vaio-12345678' + assert dev.placeholder.phid == 'a123' + assert dev.placeholder.info == 'Good conditions' + assert dev.placeholder.pallet == '24A' + assert dev.placeholder.id_device_supplier == 'TTT' From bc8944a4980c6b0fd72aa46f0cd6aa3ddd1a380e Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 5 Jul 2022 18:09:47 +0200 Subject: [PATCH 13/23] add manual edit placeholder --- ereuse_devicehub/inventory/forms.py | 165 +++++++++++++++++- ereuse_devicehub/inventory/views.py | 37 +++- ereuse_devicehub/resources/device/models.py | 28 +++ .../templates/inventory/device_create.html | 32 ++-- .../templates/inventory/device_detail.html | 3 +- .../inventory/upload_placeholder.html | 7 + requirements.txt | 2 + tests/files/placeholder_test.xls | Bin 5632 -> 6144 bytes tests/test_render_2_0.py | 142 ++++++++++++++- 9 files changed, 391 insertions(+), 25 deletions(-) diff --git a/ereuse_devicehub/inventory/forms.py b/ereuse_devicehub/inventory/forms.py index 27e29c7c..aed0d7da 100644 --- a/ereuse_devicehub/inventory/forms.py +++ b/ereuse_devicehub/inventory/forms.py @@ -334,7 +334,12 @@ class NewDeviceForm(FlaskForm): screen = FloatField('Screen size', [validators.Optional()]) def __init__(self, *args, **kwargs): + self._obj = kwargs.pop('_obj', None) super().__init__(*args, **kwargs) + if self._obj: + self.type.data = self._obj.type + if not request.form: + self.reset_from_obj() self.devices = { "Laptop": Laptop, "Desktop": Desktop, @@ -364,6 +369,45 @@ class NewDeviceForm(FlaskForm): if not self.depth.data: self.depth.data = 0.1 + def reset_from_obj(self): + if not self._obj: + return + disabled = {'disabled': "disabled"} + appearance = self._obj.appearance() + functionality = self._obj.functionality() + if appearance: + appearance = appearance.name + if functionality: + functionality = functionality.name + self.type.render_kw = disabled + self.type.data = self._obj.type + self.amount.render_kw = disabled + self.id_device_supplier.data = self._obj.placeholder.id_device_supplier + self.phid.data = self._obj.placeholder.phid + self.pallet.data = self._obj.placeholder.pallet + self.info.data = self._obj.placeholder.info + self.serial_number.data = self._obj.serial_number + self.model.data = self._obj.model + self.manufacturer.data = self._obj.manufacturer + self.appearance.data = appearance + self.functionality.data = functionality + self.brand.data = self._obj.brand + self.generation.data = self._obj.generation + self.version.data = self._obj.version + self.weight.data = self._obj.weight + self.width.data = self._obj.width + self.height.data = self._obj.height + self.depth.data = self._obj.depth + self.variant.data = self._obj.variant + self.sku.data = self._obj.sku + self.image.data = self._obj.image + if self._obj.type in ['Smartphone', 'Tablet', 'Cellphone']: + self.imei.data = self._obj.imei + self.meid.data = self._obj.meid + if self._obj.type == 'ComputerMonitor': + self.resolution.data = self._obj.resolution_width + self.screen.data = self._obj.size + def validate(self, extra_validators=None): # noqa: C901 error = ["Not a correct value"] is_valid = super().validate(extra_validators) @@ -403,7 +447,20 @@ class NewDeviceForm(FlaskForm): self.meid.errors = error is_valid = False - if self.phid.data and self.amount.data == 1: + if self.phid.data and self.amount.data == 1 and not self._obj: + dev = Placeholder.query.filter( + Placeholder.phid == self.phid.data, Device.owner == g.user + ).first() + if dev: + msg = "Sorry, exist one snapshot device with this HID" + self.phid.errors = [msg] + is_valid = False + + if ( + self.phid.data + and self._obj + and self.phid.data != self._obj.placeholder.phid + ): dev = Placeholder.query.filter( Placeholder.phid == self.phid.data, Device.owner == g.user ).first() @@ -427,9 +484,12 @@ class NewDeviceForm(FlaskForm): return True def save(self, commit=True): - for n in range(self.amount.data): - self.reset_ids() - self.create_device() + if self._obj: + self.edit_device() + else: + for n in range(self.amount.data): + self.reset_ids() + self.create_device() if commit: db.session.commit() @@ -466,7 +526,6 @@ class NewDeviceForm(FlaskForm): 'functionalityRange': self.functionality.data, } ] - # import pdb; pdb.set_trace() snapshot_json = schema.load(json_snapshot) device = snapshot_json['device'] @@ -501,6 +560,42 @@ class NewDeviceForm(FlaskForm): ) return self.placeholder + def edit_device(self): + self._obj.placeholder.phid = self.phid.data or self._obj.placeholder.phid + self._obj.placeholder.id_device_supplier = self.id_device_supplier.data or None + self._obj.placeholder.info = self.info.data or None + self._obj.placeholder.pallet = self.pallet.data or None + self._obj.model = self.model.data + self._obj.manufacturer = self.manufacturer.data + self._obj.serial_number = self.serial_number.data + self._obj.brand = self.brand.data + self._obj.version = self.version.data + self._obj.generation = self.generation.data + self._obj.sku = self.sku.data + self._obj.weight = self.weight.data + self._obj.width = self.width.data + self._obj.height = self.height.data + self._obj.depth = self.depth.data + self._obj.variant = self.variant.data + self._obj.image = self.image.data + + if self._obj.type == 'ComputerMonitor': + self._obj.resolution_width = self.resolution.data + self._obj.size = self.screen.data + + if self._obj.type in ['Smartphone', 'Tablet', 'Cellphone']: + self._obj.imei = self.imei.data + self._obj.meid = self.meid.data + + if self.appearance.data and self.appearance.data != self._obj.appearance().name: + self._obj.set_appearance(self.appearance.data) + + if ( + self.functionality.data + and self.functionality.data != self._obj.functionality().name + ): + self._obj.set_functionality(self.functionality.data) + class TagDeviceForm(FlaskForm): tag = SelectField('Tag', choices=[]) @@ -1342,18 +1437,42 @@ class UploadPlaceholderForm(FlaskForm): 'Select a Placeholder File', [validators.DataRequired()] ) + def get_data_file(self): + files = request.files.getlist(self.placeholder_file.name) + + if not files: + return False + + _file = files[0] + if _file.content_type == 'text/csv': + delimiter = ';' + data = pd.read_csv(_file).to_dict() + head = list(data.keys())[0].split(delimiter) + values = [ + {k: v.split(delimiter)} for x in data.values() for k, v in x.items() + ] + data = {} + for i in range(len(head)): + data[head[i]] = {} + for x in values: + for k, v in x.items(): + data[head[i]][k] = v[i] + else: + data = pd.read_excel(_file).to_dict() + + return data + def validate(self, extra_validators=None): is_valid = super().validate(extra_validators) if not is_valid: return False - files = request.files.getlist(self.placeholder_file.name) - - if not files: + if not request.files.getlist(self.placeholder_file.name): return False - data = pd.read_excel(files[0]).to_dict() + data = self.get_data_file() + header = [ 'Phid', 'Model', @@ -1428,3 +1547,31 @@ class UploadPlaceholderForm(FlaskForm): db.session.commit() return self.placeholders + + +class EditPlaceholderForm(FlaskForm): + manufacturer = StringField('Manufacturer', [validators.Optional()]) + model = StringField('Model', [validators.Optional()]) + serial_number = StringField('Serial Number', [validators.Optional()]) + id_device_supplier = StringField('Id Supplier', [validators.Optional()]) + phid = StringField('Phid', [validators.DataRequired()]) + pallet = StringField('Pallet', [validators.Optional()]) + info = StringField('Info', [validators.Optional()]) + + def validate(self, extra_validators=None): + is_valid = super().validate(extra_validators) + + if not is_valid: + return False + + return True + + def save(self, commit=True): + + for device in self.placeholders: + db.session.add(device) + + if commit: + db.session.commit() + + return self.placeholders diff --git a/ereuse_devicehub/inventory/views.py b/ereuse_devicehub/inventory/views.py index 975e6d4a..fad3abad 100644 --- a/ereuse_devicehub/inventory/views.py +++ b/ereuse_devicehub/inventory/views.py @@ -273,10 +273,37 @@ class DeviceCreateView(GenericMixin): return flask.render_template(self.template_name, **self.context) +class DeviceEditView(GenericMixin): + methods = ['GET', 'POST'] + decorators = [login_required] + template_name = 'inventory/device_create.html' + + def dispatch_request(self, id): + self.get_context() + device = ( + Device.query.filter(Device.owner_id == current_user.id) + .filter(Device.devicehub_id == id) + .one() + ) + form = NewDeviceForm(_obj=device) + self.context.update( + { + 'page_title': 'Edit Device', + 'form': form, + } + ) + if form.validate_on_submit(): + next_url = url_for('inventory.device_details', id=id) + form.save(commit=True) + messages.success('Device "{}" edited successfully!'.format(form.type.data)) + return flask.redirect(next_url) + + return flask.render_template(self.template_name, **self.context) + + class TagLinkDeviceView(View): methods = ['POST'] decorators = [login_required] - # template_name = 'inventory/device_list.html' def dispatch_request(self): form = TagDeviceForm() @@ -853,10 +880,11 @@ class UploadPlaceholderView(GenericMixin): if lot_id: lots = self.context['lots'] lot = lots.filter(Lot.id == lot_id).one() - for snap in snapshots: - lot.devices.add(snap.device) + for device in snapshots: + lot.devices.add(device) db.session.add(lot) db.session.commit() + messages.success('Placeholders uploaded successfully!') return flask.render_template(self.template_name, **self.context) @@ -900,6 +928,9 @@ devices.add_url_rule( '/lot//device/add/', view_func=DeviceCreateView.as_view('lot_device_add'), ) +devices.add_url_rule( + '/device/edit//', view_func=DeviceEditView.as_view('device_edit') +) devices.add_url_rule( '/tag/devices/add/', view_func=TagLinkDeviceView.as_view('tag_devices_add') ) diff --git a/ereuse_devicehub/resources/device/models.py b/ereuse_devicehub/resources/device/models.py index 1b6ae451..0a6a17e4 100644 --- a/ereuse_devicehub/resources/device/models.py +++ b/ereuse_devicehub/resources/device/models.py @@ -601,6 +601,34 @@ class Device(Thing): args[POLYMORPHIC_ON] = cls.type return args + def appearance(self): + actions = copy.copy(self.actions) + actions.sort(key=lambda x: x.created) + with suppress(LookupError, ValueError, StopIteration): + action = next(e for e in reversed(actions) if e.type == 'VisualTest') + return action.appearance_range + + def functionality(self): + actions = copy.copy(self.actions) + actions.sort(key=lambda x: x.created) + with suppress(LookupError, ValueError, StopIteration): + action = next(e for e in reversed(actions) if e.type == 'VisualTest') + return action.functionality_range + + def set_appearance(self, value): + actions = copy.copy(self.actions) + actions.sort(key=lambda x: x.created) + with suppress(LookupError, ValueError, StopIteration): + action = next(e for e in reversed(actions) if e.type == 'VisualTest') + action.appearance_range = value + + def set_functionality(self, value): + actions = copy.copy(self.actions) + actions.sort(key=lambda x: x.created) + with suppress(LookupError, ValueError, StopIteration): + action = next(e for e in reversed(actions) if e.type == 'VisualTest') + action.functionality_range = value + def is_status(self, action): from ereuse_devicehub.resources.device import states diff --git a/ereuse_devicehub/templates/inventory/device_create.html b/ereuse_devicehub/templates/inventory/device_create.html index 433d42ef..a8bcf221 100644 --- a/ereuse_devicehub/templates/inventory/device_create.html +++ b/ereuse_devicehub/templates/inventory/device_create.html @@ -34,7 +34,7 @@
-