diff --git a/ereuse_devicehub/inventory/forms.py b/ereuse_devicehub/inventory/forms.py index e7f06a0b..27e29c7c 100644 --- a/ereuse_devicehub/inventory/forms.py +++ b/ereuse_devicehub/inventory/forms.py @@ -3,6 +3,7 @@ import datetime import json from json.decoder import JSONDecodeError +import pandas as pd from boltons.urlutils import URL from flask import current_app as app from flask import g, request @@ -53,7 +54,6 @@ from ereuse_devicehub.resources.device.models import ( Smartphone, Tablet, ) -from ereuse_devicehub.resources.device.sync import Sync from ereuse_devicehub.resources.documents.models import DataWipeDocument from ereuse_devicehub.resources.enums import Severity from ereuse_devicehub.resources.hash_reports import insert_hash @@ -404,8 +404,8 @@ class NewDeviceForm(FlaskForm): is_valid = False if self.phid.data and self.amount.data == 1: - dev = Device.query.filter_by( - hid=self.phid.data, owner=g.user, active=True + 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" @@ -435,7 +435,7 @@ class NewDeviceForm(FlaskForm): db.session.commit() def create_device(self): - + schema = SnapshotSchema() json_snapshot = { 'type': 'Snapshot', 'software': 'Web', @@ -466,45 +466,20 @@ class NewDeviceForm(FlaskForm): 'functionalityRange': self.functionality.data, } ] - - upload_form = UploadSnapshotForm() - upload_form.sync = Sync() - - schema = SnapshotSchema() - self.tmp_snapshots = '/tmp/' - path_snapshot = save_json(json_snapshot, self.tmp_snapshots, g.user.email) + # import pdb; pdb.set_trace() snapshot_json = schema.load(json_snapshot) + device = snapshot_json['device'] if self.type.data == 'ComputerMonitor': - snapshot_json['device'].resolution_width = self.resolution.data - snapshot_json['device'].size = self.screen.data + device.resolution_width = self.resolution.data + device.size = self.screen.data if self.type.data in ['Smartphone', 'Tablet', 'Cellphone']: - snapshot_json['device'].imei = self.imei.data - snapshot_json['device'].meid = self.meid.data + device.imei = self.imei.data + device.meid = self.meid.data - snapshot_json['device'].placeholder = self.get_placeholder() - snapshot_json['device'].hid = self.phid.data - - snapshot = upload_form.build(snapshot_json) - move_json(self.tmp_snapshots, path_snapshot, g.user.email) - - if self.type.data == 'ComputerMonitor': - snapshot.device.resolution = self.resolution.data - snapshot.device.screen = self.screen.data - - return snapshot - - def get_phid(self): - _hid = self.phid.data - if not _hid: - _hid = Placeholder.query.order_by(Placeholder.id.desc()).first() - if _hid: - _hid = str(_hid.id + 1) - else: - _hid = '1' - - self.phid.data = _hid.lower() + device.placeholder = self.get_placeholder() + db.session.add(device) def reset_ids(self): if self.amount.data > 1: @@ -514,11 +489,11 @@ class NewDeviceForm(FlaskForm): self.sku.data = None self.imei.data = None self.meid.data = None - self.get_phid() def get_placeholder(self): self.placeholder = Placeholder( **{ + 'phid': self.phid.data or None, 'id_device_supplier': self.id_device_supplier.data, 'info': self.info.data, 'pallet': self.pallet.data, @@ -1359,3 +1334,97 @@ class NotesForm(FlaskForm): db.session.commit() return self._obj + + +class UploadPlaceholderForm(FlaskForm): + type = StringField('Type', [validators.DataRequired()]) + placeholder_file = FileField( + 'Select a Placeholder File', [validators.DataRequired()] + ) + + 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: + return False + + data = pd.read_excel(files[0]).to_dict() + header = [ + 'Phid', + 'Model', + 'Manufacturer', + 'Serial Number', + 'Id device Supplier', + 'Pallet', + 'Info', + ] + + for k in header: + if k not in data.keys(): + self.placeholder_file.errors = ["Missing required fields in the file"] + return False + + self.placeholders = [] + schema = SnapshotSchema() + self.path_snapshots = {} + for i in data['Phid'].keys(): + placeholder = None + if data['Phid'][i]: + placeholder = Placeholder.query.filter_by(phid=data['Phid'][i]).first() + + # update one + if placeholder: + device = placeholder.device + device.model = "{}".format(data['Model'][i]).lower() + device.manufacturer = "{}".format(data['Manufacturer'][i]).lower() + device.serial_number = "{}".format(data['Serial Number'][i]).lower() + placeholder.id_device_supplier = "{}".format( + data['Id device Supplier'][i] + ) + placeholder.pallet = "{}".format(data['Pallet'][i]) + placeholder.info = "{}".format(data['Info'][i]) + + self.placeholders.append(device) + continue + + # create a new one + json_snapshot = { + 'type': 'Snapshot', + 'software': 'Web', + 'version': '11.0', + 'device': { + 'type': self.type.data, + 'model': "{}".format(data['Model'][i]), + 'manufacturer': "{}".format(data['Manufacturer'][i]), + 'serialNumber': "{}".format(data['Serial Number'][i]), + }, + } + json_placeholder = { + 'phid': data['Phid'][i] or None, + 'id_device_supplier': data['Id device Supplier'][i], + 'pallet': data['Pallet'][i], + 'info': data['Info'][i], + } + + snapshot_json = schema.load(json_snapshot) + device = snapshot_json['device'] + device.placeholder = Placeholder(**json_placeholder) + + self.placeholders.append(device) + + 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 da4df610..975e6d4a 100644 --- a/ereuse_devicehub/inventory/views.py +++ b/ereuse_devicehub/inventory/views.py @@ -30,6 +30,7 @@ from ereuse_devicehub.inventory.forms import ( TradeDocumentForm, TradeForm, TransferForm, + UploadPlaceholderForm, UploadSnapshotForm, ) from ereuse_devicehub.labels.forms import PrintLabelsForm @@ -832,6 +833,34 @@ class ReceiverNoteView(GenericMixin): return flask.redirect(next_url) +class UploadPlaceholderView(GenericMixin): + methods = ['GET', 'POST'] + decorators = [login_required] + template_name = 'inventory/upload_placeholder.html' + + def dispatch_request(self, lot_id=None): + self.get_context() + form = UploadPlaceholderForm() + self.context.update( + { + 'page_title': 'Upload Placeholder', + 'form': form, + 'lot_id': lot_id, + } + ) + if form.validate_on_submit(): + snapshots = form.save(commit=False) + if lot_id: + lots = self.context['lots'] + lot = lots.filter(Lot.id == lot_id).one() + for snap in snapshots: + lot.devices.add(snap.device) + db.session.add(lot) + db.session.commit() + + return flask.render_template(self.template_name, **self.context) + + devices.add_url_rule('/action/add/', view_func=NewActionView.as_view('action_add')) devices.add_url_rule('/action/trade/add/', view_func=NewTradeView.as_view('trade_add')) devices.add_url_rule( @@ -902,3 +931,11 @@ devices.add_url_rule( '/lot//receivernote/', view_func=ReceiverNoteView.as_view('receiver_note'), ) +devices.add_url_rule( + '/upload-placeholder/', + view_func=UploadPlaceholderView.as_view('upload_placeholder'), +) +devices.add_url_rule( + '/lot//upload-placeholder/', + view_func=UploadPlaceholderView.as_view('lot_upload_placeholder'), +) diff --git a/ereuse_devicehub/migrations/versions/aeca9fb50cc6_add_placeholder.py b/ereuse_devicehub/migrations/versions/aeca9fb50cc6_add_placeholder.py index 0eef82bd..d013b64e 100644 --- a/ereuse_devicehub/migrations/versions/aeca9fb50cc6_add_placeholder.py +++ b/ereuse_devicehub/migrations/versions/aeca9fb50cc6_add_placeholder.py @@ -25,9 +25,6 @@ def get_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', @@ -44,13 +41,12 @@ def upgrade(): nullable=False, ), sa.Column('id', sa.BigInteger(), nullable=False), + sa.Column('phid', sa.Unicode(), 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()}', ) diff --git a/ereuse_devicehub/resources/device/models.py b/ereuse_devicehub/resources/device/models.py index 30c16c5b..1b6ae451 100644 --- a/ereuse_devicehub/resources/device/models.py +++ b/ereuse_devicehub/resources/device/models.py @@ -75,6 +75,13 @@ def create_code(context): return hashcode.encode(_id) +def create_phid(context): + _hid = Placeholder.query.order_by(Placeholder.id.desc()).first() + if _hid: + return str(_hid.id + 1) + return '1' + + class Device(Thing): """Base class for any type of physical object that can be identified. @@ -794,6 +801,7 @@ class DisplayMixin: class Placeholder(Thing): id = Column(BigInteger, Sequence('placeholder_seq'), primary_key=True) pallet = Column(Unicode(), nullable=True) + phid = Column(Unicode(), nullable=False, default=create_phid) pallet.comment = "used for identification where from where is this placeholders" info = db.Column(CIText()) info.comment = "more info of placeholders" @@ -814,18 +822,6 @@ class Placeholder(Thing): ) device_id.comment = "datas of the placeholder" - binding_id = db.Column( - BigInteger, - db.ForeignKey(Device.id), - nullable=True, - ) - binding = db.relationship( - Device, - backref=backref('binding', lazy=True, uselist=False), - primaryjoin=binding_id == Device.id, - ) - binding_id.comment = "device with snapshots than is linked to the placeholder" - class Computer(Device): """A chassis with components inside that can be processed diff --git a/ereuse_devicehub/static/js/create_device.js b/ereuse_devicehub/static/js/create_device.js index a6b22022..04533411 100644 --- a/ereuse_devicehub/static/js/create_device.js +++ b/ereuse_devicehub/static/js/create_device.js @@ -38,7 +38,6 @@ function amountInputs() { $("#Id_device_supplier").show(); $("#Serial_number").show(); $("#Sku").show(); - $("#imei").show(); - $("#meid").show(); + deviceInputs(); }; } diff --git a/ereuse_devicehub/templates/inventory/device_list.html b/ereuse_devicehub/templates/inventory/device_list.html index 9888dd08..2a4effe2 100644 --- a/ereuse_devicehub/templates/inventory/device_list.html +++ b/ereuse_devicehub/templates/inventory/device_list.html @@ -330,9 +330,9 @@
  • {% if lot %} - + {% else %} - + {% endif %} Upload Placeholder Spreadsheet diff --git a/ereuse_devicehub/templates/inventory/upload_placeholder.html b/ereuse_devicehub/templates/inventory/upload_placeholder.html new file mode 100644 index 00000000..9e0bd83b --- /dev/null +++ b/ereuse_devicehub/templates/inventory/upload_placeholder.html @@ -0,0 +1,117 @@ +{% extends "ereuse_devicehub/base_site.html" %} +{% block main %} + +
    +

    Inventory

    +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    Upload Placeholder
    +

    Please select a Placeholders CSV file.

    + {% if form.form_errors %} +

    + {% for error in form.form_errors %} + {{ error }}
    + {% endfor %} +

    + {% endif %} +
    +
    + Use the following template to register or update placeholders.
    + Choose the type of device.
    + The following considerations are important:
    + 1. Do not rename columns or add new columns.
    + 2. Accepted file types are ods, xlsx and csv.
    + 3. A new Placeholder will be registered if the PHID value does not exist in the system or is empty.
    + 4. A Placeholder will be updated if the PHID value exists in the system +
    + +
    + {{ form.csrf_token }} + +
    + + + 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 00000000..89db957f Binary files /dev/null and b/tests/files/placeholder_test.xls differ 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'