Merge pull request #312 from eReuse/feature/3518-define-placeholder
Feature/3518 define placeholder
This commit is contained in:
commit
676b36811d
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
)
|
||||
|
|
|
@ -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;")
|
|
@ -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;")
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
124
ereuse_devicehub/templates/inventory/upload_placeholder.html
Normal file
124
ereuse_devicehub/templates/inventory/upload_placeholder.html
Normal 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 %}
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
4
tests/files/placeholder_test.csv
Normal file
4
tests/files/placeholder_test.csv
Normal 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
|
|
BIN
tests/files/placeholder_test.ods
Normal file
BIN
tests/files/placeholder_test.ods
Normal file
Binary file not shown.
BIN
tests/files/placeholder_test.xls
Normal file
BIN
tests/files/placeholder_test.xls
Normal file
Binary file not shown.
BIN
tests/files/placeholder_test.xlsx
Normal file
BIN
tests/files/placeholder_test.xlsx
Normal file
Binary file not shown.
|
@ -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',
|
||||
|
|
|
@ -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 "Monitor" 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 "Monitor" 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 "Monitor" 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 "Monitor" 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 "Laptop" 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 "Laptop" 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 "Laptop" 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 "Laptop" 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
|
||||
|
|
|
@ -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()
|
||||
|
|
Reference in a new issue