Merge pull request #312 from eReuse/feature/3518-define-placeholder

Feature/3518 define placeholder
This commit is contained in:
cayop 2022-07-12 13:49:16 +02:00 committed by GitHub
commit 676b36811d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 1610 additions and 74 deletions

View file

@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
ml).
## testing
- [added] #312 Placeholder: new, edit, update. (manually and with excel).
## [2.3.0] - 2022-07-12
- [added] #281 Add selenium test.

View file

@ -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
@ -29,6 +30,7 @@ from wtforms.fields import FormField
from ereuse_devicehub.db import db
from ereuse_devicehub.inventory.models import DeliveryNote, ReceiverNote, Transfer
from ereuse_devicehub.parser.models import PlaceholdersLog
from ereuse_devicehub.parser.parser import ParseSnapshotLsHw
from ereuse_devicehub.parser.schemas import Snapshot_lite
from ereuse_devicehub.resources.action.models import Snapshot, Trade
@ -42,14 +44,17 @@ from ereuse_devicehub.resources.device.models import (
SAI,
Cellphone,
ComputerMonitor,
Desktop,
Device,
Keyboard,
Laptop,
MemoryCardReader,
Mouse,
Placeholder,
Server,
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
@ -300,19 +305,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()])
@ -322,8 +335,16 @@ 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,
"Server": Server,
"Smartphone": Smartphone,
"Tablet": Tablet,
"Cellphone": Cellphone,
@ -349,6 +370,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)
@ -373,12 +433,12 @@ class NewDeviceForm(FlaskForm):
self.depth.errors = error
is_valid = False
if self.imei.data:
if self.imei.data and self.amount.data == 1:
if not 13 < len(str(self.imei.data)) < 17:
self.imei.errors = error
is_valid = False
if self.meid.data:
if self.meid.data and self.amount.data == 1:
meid = self.meid.data
if not 13 < len(meid) < 17:
is_valid = False
@ -388,6 +448,28 @@ class NewDeviceForm(FlaskForm):
self.meid.errors = error
is_valid = False
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()
if dev:
msg = "Sorry, exist one snapshot device with this HID"
self.phid.errors = [msg]
is_valid = False
if not is_valid:
return False
@ -403,7 +485,18 @@ class NewDeviceForm(FlaskForm):
return True
def save(self, commit=True):
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()
def create_device(self):
schema = SnapshotSchema()
json_snapshot = {
'type': 'Snapshot',
'software': 'Web',
@ -434,33 +527,84 @@ 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)
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 = upload_form.build(snapshot_json)
device.placeholder = self.get_placeholder()
db.session.add(device)
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
placeholder_log = PlaceholdersLog(
type="New device", source='Web form', placeholder=device.placeholder
)
db.session.add(placeholder_log)
if commit:
db.session.commit()
return snapshot
def reset_ids(self):
if self.amount.data > 1:
self.phid.data = None
self.id_device_supplier.data = None
self.serial_number.data = None
self.sku.data = None
self.imei.data = None
self.meid.data = None
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,
}
)
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)
placeholder_log = PlaceholdersLog(
type="Update", source='Web form', placeholder=self._obj.placeholder
)
db.session.add(placeholder_log)
class TagDeviceForm(FlaskForm):
@ -1295,3 +1439,164 @@ 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 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':
self.source = "CSV File: {}".format(_file.filename)
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:
self.source = "Excel File: {}".format(_file.filename)
try:
data = pd.read_excel(_file).to_dict()
except ValueError:
self.placeholder_file.errors = ["File don't have a correct format"]
return False
return data
def validate(self, extra_validators=None):
is_valid = super().validate(extra_validators)
if not is_valid:
return False
if not request.files.getlist(self.placeholder_file.name):
return False
data = self.get_data_file()
if not data:
return False
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])
placeholder_log = PlaceholdersLog(
type="Update", source=self.source, placeholder=device.placeholder
)
self.placeholders.append((device, placeholder_log))
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)
placeholder_log = PlaceholdersLog(
type="New device", source=self.source, placeholder=device.placeholder
)
self.placeholders.append((device, placeholder_log))
return True
def save(self, commit=True):
for device, placeholder_log in self.placeholders:
db.session.add(device)
db.session.add(placeholder_log)
if commit:
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

View file

@ -30,10 +30,11 @@ from ereuse_devicehub.inventory.forms import (
TradeDocumentForm,
TradeForm,
TransferForm,
UploadPlaceholderForm,
UploadSnapshotForm,
)
from ereuse_devicehub.labels.forms import PrintLabelsForm
from ereuse_devicehub.parser.models import SnapshotsLog
from ereuse_devicehub.parser.models import PlaceholdersLog, SnapshotsLog
from ereuse_devicehub.resources.action.models import Trade
from ereuse_devicehub.resources.device.models import Computer, DataStorage, Device
from ereuse_devicehub.resources.documents.device_row import ActionRow, DeviceRow
@ -272,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()
@ -832,6 +860,53 @@ 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 device, p 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)
class PlaceholderLogListView(GenericMixin):
template_name = 'inventory/placeholder_log_list.html'
def dispatch_request(self):
self.get_context()
self.context['page_title'] = "Placeholder Logs"
self.context['placeholders_log'] = self.get_placeholders_log()
return flask.render_template(self.template_name, **self.context)
def get_placeholders_log(self):
placeholder_log = PlaceholdersLog.query.filter(
PlaceholdersLog.owner == g.user
).order_by(PlaceholdersLog.created.desc())
return placeholder_log
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(
@ -871,6 +946,9 @@ devices.add_url_rule(
'/lot/<string:lot_id>/device/add/',
view_func=DeviceCreateView.as_view('lot_device_add'),
)
devices.add_url_rule(
'/device/edit/<string:id>/', view_func=DeviceEditView.as_view('device_edit')
)
devices.add_url_rule(
'/tag/devices/add/', view_func=TagLinkDeviceView.as_view('tag_devices_add')
)
@ -902,3 +980,14 @@ devices.add_url_rule(
'/lot/<string:lot_id>/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/<string:lot_id>/upload-placeholder/',
view_func=UploadPlaceholderView.as_view('lot_upload_placeholder'),
)
devices.add_url_rule(
'/placeholder-logs/', view_func=PlaceholderLogListView.as_view('placeholder_logs')
)

View file

@ -0,0 +1,66 @@
"""placeholder log
Revision ID: 3e3a67f62972
Revises: aeca9fb50cc6
Create Date: 2022-07-06 18:23:54.267003
"""
import citext
import sqlalchemy as sa
from alembic import context, op
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '3e3a67f62972'
down_revision = 'aeca9fb50cc6'
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():
op.create_table(
'placeholders_log',
sa.Column(
'updated',
sa.TIMESTAMP(timezone=True),
server_default=sa.text('CURRENT_TIMESTAMP'),
nullable=False,
comment='The last time Devicehub recorded a change for \n this thing.\n ',
),
sa.Column(
'created',
sa.TIMESTAMP(timezone=True),
server_default=sa.text('CURRENT_TIMESTAMP'),
nullable=False,
comment='When Devicehub created this.',
),
sa.Column('id', sa.BigInteger(), nullable=False),
sa.Column('source', citext.CIText(), nullable=True),
sa.Column('type', citext.CIText(), nullable=True),
sa.Column('severity', sa.SmallInteger(), nullable=False),
sa.Column('placeholder_id', sa.BigInteger(), nullable=True),
sa.Column('owner_id', postgresql.UUID(as_uuid=True), nullable=False),
sa.ForeignKeyConstraint(
['placeholder_id'],
[f'{get_inv()}.placeholder.id'],
),
sa.ForeignKeyConstraint(
['owner_id'],
['common.user.id'],
),
sa.PrimaryKeyConstraint('id'),
schema=f'{get_inv()}',
)
op.execute("CREATE SEQUENCE placeholders_log_seq START 1;")
def downgrade():
op.drop_table('placeholders_log', schema=f'{get_inv()}')
op.execute("DROP SEQUENCE placeholders_log_seq;")

View file

@ -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
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('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()}',
)
op.execute("CREATE SEQUENCE placeholder_seq START 1;")
def downgrade():
op.drop_table('placeholder', schema=f'{get_inv()}')
op.execute("DROP SEQUENCE placeholder_seq;")

View file

@ -5,6 +5,7 @@ from sqlalchemy.dialects.postgresql import UUID
from ereuse_devicehub.db import db
from ereuse_devicehub.resources.action.models import Snapshot
from ereuse_devicehub.resources.device.models import Placeholder
from ereuse_devicehub.resources.enums import Severity
from ereuse_devicehub.resources.models import Thing
from ereuse_devicehub.resources.user.models import User
@ -43,3 +44,47 @@ class SnapshotsLog(Thing):
return self.snapshot.device.devicehub_id
return ''
class PlaceholdersLog(Thing):
"""A Placeholder log."""
id = Column(BigInteger, Sequence('placeholders_log_seq'), primary_key=True)
source = Column(CIText(), default='', nullable=True)
type = Column(CIText(), default='', nullable=True)
severity = Column(SmallInteger, default=Severity.Info, nullable=False)
placeholder_id = Column(BigInteger, db.ForeignKey(Placeholder.id), nullable=True)
placeholder = db.relationship(
Placeholder, primaryjoin=placeholder_id == Placeholder.id
)
owner_id = db.Column(
UUID(as_uuid=True),
db.ForeignKey(User.id),
nullable=False,
default=lambda: g.user.id,
)
owner = db.relationship(User, primaryjoin=owner_id == User.id)
def save(self, commit=False):
db.session.add(self)
if commit:
db.session.commit()
@property
def phid(self):
if self.placeholder:
return self.placeholder.phid
return ''
@property
def dhid(self):
if self.placeholder:
return self.placeholder.device.devicehub_id
return ''
def get_status(self):
return Severity(self.severity)

View file

@ -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.
@ -594,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
@ -791,6 +826,43 @@ class DisplayMixin:
return v
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"
id_device_supplier = db.Column(CIText())
id_device_supplier.comment = (
"Identification used for one supplier of one placeholders"
)
device_id = db.Column(
BigInteger,
db.ForeignKey(Device.id),
nullable=False,
)
device = db.relationship(
Device,
backref=backref('placeholder', lazy=True, uselist=False),
primaryjoin=device_id == Device.id,
)
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 = "binding placeholder with workbench device"
class Computer(Device):
"""A chassis with components inside that can be processed
automatically with Workbench Computer.

View file

@ -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

View file

@ -1,6 +1,8 @@
$(document).ready(() => {
$("#type").on("change", deviceInputs);
$("#amount").on("change", amountInputs);
deviceInputs();
amountInputs();
})
function deviceInputs() {
@ -19,5 +21,23 @@ function deviceInputs() {
$("#resolution").hide();
$("#imei").hide();
$("#meid").hide();
}
};
amountInputs();
}
function amountInputs() {
if ($("#amount").val() > 1) {
$("#Phid").hide();
$("#Id_device_supplier").hide();
$("#Serial_number").hide();
$("#Sku").hide();
$("#imei").hide();
$("#meid").hide();
} else {
$("#Phid").show();
$("#Id_device_supplier").show();
$("#Serial_number").show();
$("#Sku").show();
deviceInputs();
};
}

View file

@ -148,6 +148,15 @@
</a>
</li>
<li class="nav-heading">Placeholders</li>
<li class="nav-item">
<a class="nav-link collapsed" href="{{ url_for('inventory.placeholder_logs') }}">
<i class="bi-menu-button-wide"></i>
<span>Uploaded Placeholders</span>
</a>
</li>
<li class="nav-heading">Devices</li>
<li class="nav-item">

View file

@ -34,8 +34,16 @@
<div>
<div class="form-group has-validation mb-2">
<label for="name" class="form-label">Type *</label>
<select id="type" class="form-control" name="type" required="">
<select id="type" class="form-control" name="type" required="" {% if form.type.render_kw.disabled %}disabled="disabled"{% endif %}>
<option value="">Select one Type</option>
<optgroup label="Computer">
<option value="Laptop"
{% if form.type.data == 'Laptop' %} selected="selected"{% endif %}>Laptop</option>
<option value="Desktop"
{% if form.type.data == 'Desktop' %} selected="selected"{% endif %}>Desktop</option>
<option value="Server"
{% if form.type.data == 'Server' %} selected="selected"{% endif %}>Server</option>
</optgroup>
<optgroup label="Monitor">
<option value="ComputerMonitor"
{% if form.type.data == 'Monitor' %} selected="selected"{% endif %}>Computer Monitor</option>
@ -70,20 +78,72 @@
</div>
<div class="form-group mb-2">
<label for="label" class="form-label">Label</label>
{{ form.label(class_="form-control") }}
<small class="text-muted form-text">Label that you want link to this device</small>
{% if form.label.errors %}
<label for="label" class="form-label">{{ form.amount.label }} *</label>
{{ form.amount(class_="form-control") }}
<small class="text-muted form-text">Number of devices you want to create of this type</small>
{% if form.amount.errors %}
<p class="text-danger">
{% for error in form.label.errors %}
{% for error in form.amount.errors %}
{{ error }}<br/>
{% endfor %}
</p>
{% endif %}
</div>
<div class="from-group has-validation mb-2">
<label for="serialNumber" class="form-label">{{ form.serial_number.label }} *</label>
<div class="form-group mb-2" id="Phid">
<label for="label" class="form-label">{{ form.phid.label }}</label>
{{ form.phid(class_="form-control") }}
<small class="text-muted form-text">Label that you want link to this device</small>
{% if form.phid.errors %}
<p class="text-danger">
{% for error in form.phid.errors %}
{{ error }}<br/>
{% endfor %}
</p>
{% endif %}
</div>
<div class="form-group mb-2" id="Id_device_supplier">
<label for="label" class="form-label">{{ form.id_device_supplier.label }}</label>
{{ form.id_device_supplier(class_="form-control") }}
<small class="text-muted form-text">Identity of device for the Supplier</small>
{% if form.id_device_supplier.errors %}
<p class="text-danger">
{% for error in form.id_device_supplier.errors %}
{{ error }}<br/>
{% endfor %}
</p>
{% endif %}
</div>
<div class="form-group mb-2">
<label for="label" class="form-label">{{ form.pallet.label }}</label>
{{ form.pallet(class_="form-control") }}
<small class="text-muted form-text">Identity of pallet</small>
{% if form.pallet.errors %}
<p class="text-danger">
{% for error in form.pallet.errors %}
{{ error }}<br/>
{% endfor %}
</p>
{% endif %}
</div>
<div class="form-group mb-2">
<label for="label" class="form-label">{{ form.info.label }}</label>
{{ form.info(class_="form-control") }}
<small class="text-muted form-text">Info extra</small>
{% if form.info.errors %}
<p class="text-danger">
{% for error in form.info.errors %}
{{ error }}<br/>
{% endfor %}
</p>
{% endif %}
</div>
<div class="from-group has-validation mb-2" id="Serial_number">
<label for="serialNumber" class="form-label">{{ form.serial_number.label }}</label>
{{ form.serial_number(class_="form-control") }}
<small class="text-muted form-text">Serial number of this device</small>
{% if form.serial_number.errors %}
@ -96,7 +156,7 @@
</div>
<div class="from-group has-validation mb-2">
<label for="model" class="form-label">{{ form.model.label }} *</label>
<label for="model" class="form-label">{{ form.model.label }}</label>
{{ form.model(class_="form-control") }}
<small class="text-muted form-text">Name of model</small>
{% if form.model.errors %}
@ -109,7 +169,7 @@
</div>
<div class="from-group has-validation mb-2">
<label for="model" class="form-label">{{ form.manufacturer.label }} *</label>
<label for="model" class="form-label">{{ form.manufacturer.label }}</label>
{{ form.manufacturer(class_="form-control") }}
<small class="text-muted form-text">Name of manufacturer</small>
{% if form.manufacturer.errors %}
@ -124,27 +184,33 @@
<div class="from-group has-validation mb-2">
<label for="model" class="form-label">{{ form.appearance.label }}</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="appearance" value="Z">
<input class="form-check-input" type="radio" name="appearance" value="Z"
{% if form.appearance.data == 'Z' %}checked="cheked"{% endif %}>
<label class="form-check-label">0. The device is new.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="appearance" value="A">
<input class="form-check-input" type="radio" name="appearance" value="A"
{% if form.appearance.data == 'A' %}checked="checked"{% endif %}>
<label class="form-check-label">A. Like new (no visual damage))</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="appearance" value="B">
<input class="form-check-input" type="radio" name="appearance" value="B"
{% if form.appearance.data == 'B' %}checked="checked"{% endif %}>
<label class="form-check-label">B. In very good condition (small visual damage to hard-to-detect parts)</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="appearance" value="C">
<input class="form-check-input" type="radio" name="appearance" value="C"
{% if form.appearance.data == 'C' %}checked="checked"{% endif %}>
<label class="form-check-label">C. In good condition (small visual damage to easy-to-detect parts, not the screen))</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="appearance" value="D">
<input class="form-check-input" type="radio" name="appearance" value="D"
{% if form.appearance.data == 'D' %}checked="checked"{% endif %}>
<label class="form-check-label">D. It is acceptable (visual damage to visible parts, not on the screen)</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="appearance" value="E">
<input class="form-check-input" type="radio" name="appearance" value="E"
{% if form.appearance.data == 'E' %}checked="checked"{% endif %}>
<label class="form-check-label">E. It is unacceptable (substantial visual damage that may affect use)</label>
</div>
<small class="text-muted form-text">Rate the imperfections that affect the device aesthetically, but not its use.</small>
@ -160,19 +226,23 @@
<div class="from-group has-validation mb-2">
<label for="model" class="form-label">{{ form.functionality.label }}</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="functionality" value="A">
<input class="form-check-input" type="radio" name="functionality" value="A"
{% if form.functionality.data == 'A' %}checked="checked"{% endif %}>
<label class="form-check-label">A. Everything works perfectly (buttons, and no scratches on the screen)</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="functionality" value="B">
<input class="form-check-input" type="radio" name="functionality" value="B"
{% if form.functionality.data == 'B' %}checked="checked"{% endif %}>
<label class="form-check-label">B. There is a hard to press button or small scratches on the corners of the screen</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="functionality" value="C">
<input class="form-check-input" type="radio" name="functionality" value="C"
{% if form.functionality.data == 'C' %}checked="checked"{% endif %}>
<label class="form-check-label">C. A non-essential button does not work; the screen has multiple scratches on the corners</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="functionality" value="D">
<input class="form-check-input" type="radio" name="functionality" value="D"
{% if form.functionality.data == 'D' %}checked="checked"{% endif %}>
<label class="form-check-label">D. Multiple buttons do not work properly; the screen has severe damage that may affect use</label>
</div>
<small class="text-muted form-text">It qualifies the defects of a device that affect its use.</small>
@ -199,7 +269,7 @@
</div>
<div class="from-group has-validation mb-2">
<label for="model" class="form-label">{{ form.generation.label }} *</label>
<label for="model" class="form-label">{{ form.generation.label }}</label>
{{ form.generation(class_="form-control") }}
<small class="text-muted form-text">The generation of the device.</small>
{% if form.generation.errors %}
@ -225,7 +295,7 @@
</div>
<div class="from-group has-validation mb-2">
<label for="model" class="form-label">{{ form.weight.label }} *</label>
<label for="model" class="form-label">{{ form.weight.label }}</label>
{{ form.weight(class_="form-control") }}
<small class="text-muted form-text">The weight of the device in Kg.</small>
{% if form.weight.errors %}
@ -238,7 +308,7 @@
</div>
<div class="from-group has-validation mb-2">
<label for="model" class="form-label">{{ form.width.label }} *</label>
<label for="model" class="form-label">{{ form.width.label }}</label>
{{ form.width(class_="form-control") }}
<small class="text-muted form-text">The width of the device in meters.</small>
{% if form.width.errors %}
@ -251,7 +321,7 @@
</div>
<div class="from-group has-validation mb-2">
<label for="model" class="form-label">{{ form.height.label }} *</label>
<label for="model" class="form-label">{{ form.height.label }}</label>
{{ form.height(class_="form-control") }}
<small class="text-muted form-text">The height of the device in meters.</small>
{% if form.height.errors %}
@ -264,7 +334,7 @@
</div>
<div class="from-group has-validation mb-2">
<label for="model" class="form-label">{{ form.depth.label }} *</label>
<label for="model" class="form-label">{{ form.depth.label }}</label>
{{ form.depth(class_="form-control") }}
<small class="text-muted form-text">The depth of the device in meters.</small>
{% if form.depth.errors %}
@ -289,7 +359,7 @@
{% endif %}
</div>
<div class="from-group has-validation mb-2">
<div class="from-group has-validation mb-2" id="Sku">
<label for="model" class="form-label">{{ form.sku.label }}</label>
{{ form.sku(class_="form-control") }}
<small class="text-muted form-text">The Stock Keeping Unit (SKU), i.e. a merchant-specific identifier for a product or service.</small>

View file

@ -54,7 +54,8 @@
<div class="tab-content pt-2">
<div class="tab-pane fade show active" id="type">
<h5 class="card-title">Type Details</h5>
<h5 class="card-title">Details</h5>
{% if device.placeholder %}(<a href="{{ url_for('inventory.device_edit', id=device.devicehub_id)}}">edit</a>){% endif %}
<div class="row">
<div class="col-lg-3 col-md-4 label ">Type</div>

View file

@ -315,7 +315,7 @@
<div class="btn-group dropdown m-1" uib-dropdown="">
<button id="btnSnapshot" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-laptop"></i>
New Device
New Devices
</button>
<ul class="dropdown-menu" aria-labelledby="btnSnapshot">
<li>
@ -325,7 +325,17 @@
<a href="{{ url_for('inventory.upload_snapshot') }}" class="dropdown-item">
{% endif %}
<i class="bi bi-upload"></i>
Upload a new Snapshot
Upload Snapshot files
</a>
</li>
<li>
{% if lot %}
<a href="{{ url_for('inventory.lot_upload_placeholder', lot_id=lot.id) }}" class="dropdown-item">
{% else %}
<a href="{{ url_for('inventory.upload_placeholder') }}" class="dropdown-item">
{% endif %}
<i class="bi bi-upload"></i>
Upload Placeholder Spreadsheet
</a>
</li>
<li>
@ -335,7 +345,7 @@
<a href="{{ url_for('inventory.device_add') }}" class="dropdown-item">
{% endif %}
<i class="bi bi-plus"></i>
Create a new Device
Create a new Placeholder
</a>
</li>
</ul>

View file

@ -0,0 +1,80 @@
{% extends "ereuse_devicehub/base_site.html" %}
{% block main %}
<div class="pagetitle">
<h1>{{ page_title }}</h1>
<nav>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ url_for('inventory.devicelist')}}">Inventory</a></li>
<li class="breadcrumb-item active">Placeholders</li>
</ol>
</nav>
</div><!-- End Page Title -->
<section class="section profile">
<div class="row">
<div class="col-xl-12">
<div class="card">
<div class="card-body pt-3" style="min-height: 650px;">
<!-- Bordered Tabs -->
<div class="tab-content pt-5">
<div id="devices-list" class="tab-pane fade devices-list active show">
<div class="tab-content pt-2">
<table class="table">
<thead>
<tr>
<th scope="col">PHID</th>
<th scope="col">Placeholder source</th>
<th scope="col">Type</th>
<th scope="col">DHID</th>
<th scope="col">Status</th>
<th scope="col" data-type="date" data-format="DD-MM-YYYY">Time</th>
</tr>
</thead>
<tbody>
{% for log in placeholders_log %}
<tr>
<td>
{{ log.phid }}
</td>
<td>
{{ log.source }}
</td>
<td>
{{ log.type }}
</td>
<td>
{% if log.dhid %}
<a href="{{ url_for('inventory.device_details', id=log.dhid)}}">{{ log.dhid }}</a>
{% endif %}
</td>
<td>
{{ log.get_status() }}
</td>
<td>{{ log.created.strftime('%H:%M %d-%m-%Y') }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div><!-- End Bordered Tabs -->
</div>
</div>
</div>
<div id="NotificationsContainer" style="position: absolute; bottom: 0; right: 0; margin: 10px; margin-top: 70px; width: calc(100% - 310px);"></div>
</div>
</div>
</section>
<!-- Custom Code -->
<script>
const table = new simpleDatatables.DataTable("table")
</script>
{% endblock main %}

View file

@ -0,0 +1,124 @@
{% extends "ereuse_devicehub/base_site.html" %}
{% block main %}
<div class="pagetitle">
<h1>Inventory</h1>
<nav>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ url_for('inventory.devicelist')}}">Inventory</a></li>
<li class="breadcrumb-item active">{{ page_title }}</li>
</ol>
</nav>
</div><!-- End Page Title -->
<section class="section profile">
<div class="row">
<div class="col-xl-8">
<div class="card">
<div class="card-body">
<div class="pt-4 pb-2">
<h5 class="card-title text-center pb-0 fs-4">Upload Placeholder</h5>
<p class="text-center small">Please select a Placeholders CSV file.</p>
{% if form.form_errors %}
<p class="text-danger">
{% for error in form.form_errors %}
{{ error }}<br/>
{% endfor %}
</p>
{% endif %}
</div>
<div class="pt-4 pb-2 mb-3">
Use the following template to register or update placeholders.<br />
Choose the type of device.<br />
The following considerations are important:<br />
1. Do not rename columns or add new columns.<br />
2. Accepted file types are ods, xlsx and csv.<br />
3. A new Placeholder will be registered if the PHID value does not exist in the system or is empty.<br />
4. A Placeholder will be updated if the PHID value exists in the system
</div>
<form method="post" enctype="multipart/form-data" class="row g-3 needs-validation" novalidate>
{{ form.csrf_token }}
<div class="form-group has-validation mb-2">
<label for="name" class="form-label">Type *</label>
<select id="type" class="form-control" name="type" required="">
<option value="">Select one Type</option>
<optgroup label="Computer">
<option value="Laptop"
{% if form.type.data == 'Laptop' %} selected="selected"{% endif %}>Laptop</option>
<option value="Desktop"
{% if form.type.data == 'Desktop' %} selected="selected"{% endif %}>Desktop</option>
<option value="Server"
{% if form.type.data == 'Server' %} selected="selected"{% endif %}>Server</option>
</optgroup>
<optgroup label="Monitor">
<option value="ComputerMonitor"
{% if form.type.data == 'Monitor' %} selected="selected"{% endif %}>Computer Monitor</option>
</optgroup>
<optgroup label="Mobile">
<option value="Smartphone"
{% if form.type.data == 'Smartphone' %} selected="selected"{% endif %}>Smartphone</option>
<option value="Tablet"
{% if form.type.data == 'Tablet' %} selected="selected"{% endif %}>Tablet</option>
<option value="Cellphone"
{% if form.type.data == 'Cellphone' %} selected="selected"{% endif %}>Cellphone</option>
</optgroup>
<optgroup label="Computer Accessory">
<option value="Mouse"
{% if form.type.data == 'Mouse' %} selected="selected"{% endif %}>Mouse</option>
<option value="MemoryCardReader"
{% if form.type.data == 'MemoryCardReader' %} selected="selected"{% endif %}>Memory card reader</option>
<option value="SAI"
{% if form.type.data == 'SAI' %} selected="selected"{% endif %}>SAI</option>
<option value="Keyboard"
{% if form.type.data == 'Keyboard' %} selected="selected"{% endif %}>Keyboard</option>
</optgroup>
</select>
<small class="text-muted form-text">Type of devices</small>
{% if form.type.errors %}
<p class="text-danger">
{% for error in form.type.errors %}
{{ error }}<br/>
{% endfor %}
</p>
{% endif %}
</div>
<div>
<label for="name" class="form-label">Select a Placeholders CSV file *</label>
<div class="input-group has-validation">
{{ form.placeholder_file }}
</div>
{% if form.placeholder_file.errors %}
<p class="text-danger">
{% for error in form.placeholder_file.errors %}
{{ error }}<br/>
{% endfor %}
</p>
{% endif %}
</div>
<div>
{% if lot_id %}
<a href="{{ url_for('inventory.lotdevicelist', lot_id=lot_id) }}" class="btn btn-danger">Cancel</a>
{% else %}
<a href="{{ url_for('inventory.devicelist') }}" class="btn btn-danger">Cancel</a>
{% endif %}
<button class="btn btn-primary" type="submit">Send</button>
</div>
</form>
</div>
</div>
</div>
<div class="col-xl-8">
</div>
</div>
</section>
{% endblock main %}

View file

@ -5,9 +5,9 @@ Use this as a starting point.
"""
import sentry_sdk
from decouple import config
from flask_wtf.csrf import CSRFProtect
# from flask_wtf.csrf import CSRFProtect
from sentry_sdk.integrations.flask import FlaskIntegration
from werkzeug.contrib.profiler import ProfilerMiddleware
from ereuse_devicehub.api.views import api
from ereuse_devicehub.config import DevicehubConfig
@ -17,6 +17,9 @@ from ereuse_devicehub.labels.views import labels
from ereuse_devicehub.views import core
from ereuse_devicehub.workbench.views import workbench
# from werkzeug.contrib.profiler import ProfilerMiddleware
SENTRY_DSN = config('SENTRY_DSN', None)
if SENTRY_DSN:
sentry_sdk.init(
@ -41,10 +44,10 @@ app.register_blueprint(workbench)
# configure & enable CSRF of Flask-WTF
# NOTE: enable by blueprint to exclude API views
# TODO(@slamora: enable by default & exclude API views when decouple of Teal is completed
csrf = CSRFProtect(app)
# csrf = CSRFProtect(app)
# csrf.protect(core)
# csrf.protect(devices)
app.config["SQLALCHEMY_RECORD_QUERIES"] = True
app.config['PROFILE'] = True
app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[30])
app.run(debug=True)
# app.config["SQLALCHEMY_RECORD_QUERIES"] = True
# app.config['PROFILE'] = True
# app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[30])
# app.run(debug=True)

View file

@ -46,5 +46,11 @@ selenium==4.1.5
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
openpyxl==3.0.10
et_xmlfile==1.1.0
sentry_sdk==1.6.0
blinker==1.4

View file

@ -0,0 +1,4 @@
Phid;Model;Manufacturer;Serial Number;Id device Supplier;Pallet;Info
a123;Vaio;Sony;12345678;TTT;24A;Good conditions
a124;Vaio;Sony;12345679;TTT;24A;Good conditions
a125;Vaio;Sony;12345680;TTT;24A;Good conditions
1 Phid Model Manufacturer Serial Number Id device Supplier Pallet Info
2 a123 Vaio Sony 12345678 TTT 24A Good conditions
3 a124 Vaio Sony 12345679 TTT 24A Good conditions
4 a125 Vaio Sony 12345680 TTT 24A Good conditions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -72,6 +72,10 @@ def test_api_docs(client: Client):
'/inventory/tag/devices/add/',
'/inventory/tag/devices/{id}/del/',
'/inventory/upload-snapshot/',
'/inventory/device/edit/{id}/',
'/inventory/upload-placeholder/',
'/inventory/lot/{lot_id}/upload-placeholder/',
'/inventory/placeholder-logs/',
'/labels/',
'/labels/add/',
'/labels/print',

View file

@ -445,12 +445,227 @@ def test_add_monitor(user3: UserClientFlask):
'weight': 0.1,
'height': 0.1,
'depth': 0.1,
'id_device_supplier': "b2",
}
body, status = user3.post(uri, data=data)
assert status == '200 OK'
assert 'Device &#34;Monitor&#34; created successfully!' in body
dev = Device.query.one()
assert dev.type == 'Monitor'
assert dev.placeholder.id_device_supplier == "b2"
assert dev.hid == 'monitor-samsung-lc27t55-aaaab'
assert dev.placeholder.phid == '1'
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_update_monitor(user3: UserClientFlask):
uri = '/inventory/device/add/'
body, status = user3.get(uri)
assert status == '200 OK'
assert "New Device" in body
data = {
'csrf_token': generate_csrf(),
'type': "Monitor",
'serial_number': "AAAAB",
'model': "LC27T55",
'manufacturer': "Samsung",
'generation': 1,
'weight': 0.1,
'height': 0.1,
'depth': 0.1,
'id_device_supplier': "b2",
'pallet': "l34",
}
body, status = user3.post(uri, data=data)
assert status == '200 OK'
assert 'Device &#34;Monitor&#34; created successfully!' in body
dev = Device.query.one()
assert dev.type == 'Monitor'
assert dev.placeholder.id_device_supplier == "b2"
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"
data = {
'csrf_token': generate_csrf(),
'type': "Monitor",
'phid': '1',
'serial_number': "AAAAB",
'model': "LCD 43 b",
'manufacturer': "Samsung",
'generation': 1,
'weight': 0.1,
'height': 0.1,
'depth': 0.2,
'id_device_supplier': "b3",
'pallet': "l20",
}
body, status = user3.post(uri, data=data)
assert status == '200 OK'
assert 'Sorry, exist one snapshot device with this HID' in body
dev = Device.query.one()
assert dev.type == 'Monitor'
assert dev.placeholder.id_device_supplier == "b2"
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"
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_add_2_monitor(user3: UserClientFlask):
uri = '/inventory/device/add/'
body, status = user3.get(uri)
assert status == '200 OK'
assert "New Device" in body
data = {
'csrf_token': generate_csrf(),
'type': "Monitor",
'phid': "AAB",
'serial_number': "AAAAB",
'model': "LC27T55",
'manufacturer': "Samsung",
'generation': 1,
'weight': 0.1,
'height': 0.1,
'depth': 0.1,
'id_device_supplier': "b1",
'pallet': "l34",
}
body, status = user3.post(uri, data=data)
assert status == '200 OK'
assert 'Device &#34;Monitor&#34; created successfully!' in body
dev = Device.query.one()
assert dev.type == 'Monitor'
assert dev.placeholder.id_device_supplier == "b1"
assert dev.hid == 'monitor-samsung-lc27t55-aaaab'
assert dev.placeholder.phid == 'AAB'
assert dev.model == 'lc27t55'
assert dev.placeholder.pallet == "l34"
data = {
'csrf_token': generate_csrf(),
'type': "Monitor",
'serial_number': "AAAAB",
'model': "LCD 43 b",
'manufacturer': "Samsung",
'generation': 1,
'weight': 0.1,
'height': 0.1,
'depth': 0.2,
'id_device_supplier': "b2",
'pallet': "l20",
}
body, status = user3.post(uri, data=data)
assert status == '200 OK'
assert 'Device &#34;Monitor&#34; created successfully!' in body
dev = Device.query.all()[-1]
assert dev.type == 'Monitor'
assert dev.placeholder.id_device_supplier == "b2"
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"
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_add_laptop(user3: UserClientFlask):
uri = '/inventory/device/add/'
body, status = user3.get(uri)
assert status == '200 OK'
assert "New Device" in body
data = {
'csrf_token': generate_csrf(),
'type': "Laptop",
'serial_number': "AAAAB",
'model': "LC27T55",
'manufacturer': "Samsung",
'generation': 1,
'weight': 0.1,
'height': 0.1,
'depth': 0.1,
'id_device_supplier': "b2",
}
body, status = user3.post(uri, data=data)
assert status == '200 OK'
assert 'Device &#34;Laptop&#34; created successfully!' in body
dev = Device.query.one()
assert dev.type == 'Laptop'
assert dev.placeholder.id_device_supplier == "b2"
assert dev.hid == 'laptop-samsung-lc27t55-aaaab'
assert dev.placeholder.phid == '1'
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_add_with_ammount_laptops(user3: UserClientFlask):
uri = '/inventory/device/add/'
body, status = user3.get(uri)
assert status == '200 OK'
assert "New Device" in body
num = 3
data = {
'csrf_token': generate_csrf(),
'type': "Laptop",
'amount': num,
'serial_number': "AAAAB",
'model': "LC27T55",
'manufacturer': "Samsung",
'generation': 1,
'weight': 0.1,
'height': 0.1,
'depth': 0.1,
'id_device_supplier': "b2",
}
body, status = user3.post(uri, data=data)
assert status == '200 OK'
assert 'Device &#34;Laptop&#34; created successfully!' in body
for dev in Device.query.all():
assert dev.type == 'Laptop'
assert dev.placeholder.id_device_supplier is None
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):
uri = '/inventory/device/add/'
body, status = user3.get(uri)
assert status == '200 OK'
assert "New Device" in body
data = {
'csrf_token': generate_csrf(),
'type': "Laptop",
'phid': 'laptop-asustek_computer_inc-1001pxd-b8oaas048285-14:da:e9:42:f6:7b',
'serial_number': "AAAAB",
'model': "LC27T55",
'manufacturer': "Samsung",
'generation': 1,
'weight': 0.1,
'height': 0.1,
'depth': 0.1,
}
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() == 1
@pytest.mark.mvp
@ -1400,3 +1615,326 @@ 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() == 3
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'
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_add_placeholder_csv(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.csv')
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() == 3
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'
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_add_placeholder_ods(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.ods')
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() == 3
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'
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_add_placeholder_office_open_xml(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.xlsx')
)
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() == 3
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'
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_edit_laptop(user3: UserClientFlask):
uri = '/inventory/device/add/'
body, status = user3.get(uri)
assert status == '200 OK'
assert "New Device" in body
data = {
'csrf_token': generate_csrf(),
'type': "Laptop",
'serial_number': "AAAAB",
'model': "LC27T55",
'manufacturer': "Samsung",
'generation': 1,
'weight': 0.1,
'height': 0.1,
'depth': 0.1,
'id_device_supplier': "b2",
}
body, status = user3.post(uri, data=data)
assert status == '200 OK'
assert 'Device &#34;Laptop&#34; created successfully!' in body
dev = Device.query.one()
assert dev.type == 'Laptop'
assert dev.hid == 'laptop-samsung-lc27t55-aaaab'
assert dev.placeholder.phid == '1'
assert dev.placeholder.id_device_supplier == 'b2'
assert dev.serial_number == 'aaaab'
assert dev.model == 'lc27t55'
uri = '/inventory/device/edit/{}/'.format(dev.devicehub_id)
body, status = user3.get(uri)
assert status == '200 OK'
assert "Edit Device" in body
data = {
'csrf_token': generate_csrf(),
'type': "Laptop",
'serial_number': "AAAAC",
'model': "LC27T56",
'manufacturer': "Samsung",
'generation': 1,
'weight': 0.1,
'height': 0.1,
'depth': 0.1,
'id_device_supplier': "a2",
}
body, status = user3.post(uri, data=data)
assert status == '200 OK'
assert 'Device &#34;Laptop&#34; edited successfully!' in body
dev = Device.query.one()
assert dev.type == 'Laptop'
assert dev.hid == 'laptop-samsung-lc27t55-aaaab'
assert dev.placeholder.phid == '1'
assert dev.placeholder.id_device_supplier == 'a2'
assert dev.serial_number == 'aaaac'
assert dev.model == 'lc27t56'
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_placeholder_log_manual_new(user3: UserClientFlask):
uri = '/inventory/device/add/'
user3.get(uri)
data = {
'csrf_token': generate_csrf(),
'type': "Laptop",
'phid': 'ace',
'serial_number': "AAAAB",
'model': "LC27T55",
'manufacturer': "Samsung",
'generation': 1,
'weight': 0.1,
'height': 0.1,
'depth': 0.1,
'id_device_supplier': "b2",
}
user3.post(uri, data=data)
uri = '/inventory/placeholder-logs/'
body, status = user3.get(uri)
assert status == '200 OK'
assert "Placeholder Logs" in body
assert "Web form" in body
assert "ace" in body
assert "New device" in body
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_placeholder_log_manual_edit(user3: UserClientFlask):
uri = '/inventory/device/add/'
user3.get(uri)
data = {
'csrf_token': generate_csrf(),
'type': "Laptop",
'phid': 'ace',
'serial_number': "AAAAB",
'model': "LC27T55",
'manufacturer': "Samsung",
'generation': 1,
'weight': 0.1,
'height': 0.1,
'depth': 0.1,
'id_device_supplier': "b2",
}
user3.post(uri, data=data)
dev = Device.query.one()
uri = '/inventory/device/edit/{}/'.format(dev.devicehub_id)
user3.get(uri)
data = {
'csrf_token': generate_csrf(),
'type': "Laptop",
'serial_number': "AAAAC",
'model': "LC27T56",
'manufacturer': "Samsung",
'generation': 1,
'weight': 0.1,
'height': 0.1,
'depth': 0.1,
'id_device_supplier': "a2",
}
user3.post(uri, data=data)
uri = '/inventory/placeholder-logs/'
body, status = user3.get(uri)
assert status == '200 OK'
assert "Placeholder Logs" in body
assert "Web form" in body
assert "ace" in body
assert "Update" in body
assert dev.devicehub_id in body
assert "" in body
assert "CSV" not in body
assert "Excel" not in body
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_placeholder_log_excel_new(user3: UserClientFlask):
uri = '/inventory/upload-placeholder/'
body, status = user3.get(uri)
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")
dev = Device.query.first()
assert dev.placeholder.phid == 'a123'
uri = '/inventory/placeholder-logs/'
body, status = user3.get(uri)
assert status == '200 OK'
assert "Placeholder Logs" in body
assert dev.placeholder.phid in body
assert dev.devicehub_id in body
assert "Web form" not in body
assert "Update" not in body
assert "New device" in body
assert "" in body
assert "CSV" not in body
assert "Excel" in body
assert "placeholder_test.xls" in body
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_placeholder_log_excel_update(user3: UserClientFlask):
uri = '/inventory/upload-placeholder/'
body, status = user3.get(uri)
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")
file_path = Path(__file__).parent.joinpath('files').joinpath('placeholder_test.csv')
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")
dev = Device.query.first()
assert dev.placeholder.phid == 'a123'
uri = '/inventory/placeholder-logs/'
body, status = user3.get(uri)
assert status == '200 OK'
assert "Placeholder Logs" in body
assert dev.placeholder.phid in body
assert dev.devicehub_id in body
assert "Web form" not in body
assert "Update" in body
assert "New device" in body
assert "" in body
assert "CSV" in body
assert "Excel" in body
assert "placeholder_test.xls" in body
assert "placeholder_test.csv" in body

View file

@ -42,10 +42,12 @@ class TestSelenium:
lot_id = self.driver.current_url.split("/")[5]
# go to unassigned
self.driver.find_element(By.CSS_SELECTOR, ".nav-item:nth-child(5) span").click()
# self.driver.find_element(By.CSS_SELECTOR, ".nav-item:nth-child(5) span").click()
self.driver.find_element(By.CSS_SELECTOR, ".nav-item:nth-child(7) span").click()
self.driver.implicitly_wait(3)
# select the first device
self.driver.find_element(
By.CSS_SELECTOR, "tr:nth-child(1) .deviceSelect"
).click()
@ -95,10 +97,14 @@ class TestSelenium:
self.driver.find_element(By.ID, "SaveAllActions").click()
time.sleep(3)
self.driver.find_element(By.CSS_SELECTOR, ".nav-item:nth-child(5) span").click()
# self.driver.find_element(By.CSS_SELECTOR, ".nav-item:nth-child(5) span").click()
self.driver.find_element(By.CSS_SELECTOR, ".nav-item:nth-child(7) span").click()
self.driver.implicitly_wait(3)
# logout
# self.driver.find_element(By.CSS_SELECTOR, ".d-md-block:nth-child(2)").click()
self.driver.find_element(By.CSS_SELECTOR, ".d-md-block:nth-child(2)").click()
self.driver.implicitly_wait(3)
self.driver.find_element(By.LINK_TEXT, "Sign Out").click()
self.driver.find_element(By.CSS_SELECTOR, "li:nth-child(9) > .dropdown-item > span").click()
# self.driver.find_element(By.CSS_SELECTOR, ".d-md-block").click()
# self.driver.implicitly_wait(3)
# self.driver.find_element(By.LINK_TEXT, "Sign Out").click()