Merge branch 'testing' into feature/3729-user-registration

This commit is contained in:
Cayo Puigdefabregas 2022-10-18 17:15:28 +02:00
commit c2d81b1b29
46 changed files with 1428 additions and 561 deletions

View File

@ -1,10 +1,8 @@
name: Selenium
on:
push:
branches: [master, testing]
pull_request:
branches: [master, testing]
types: [ready_for_review, review_requested]
jobs:
build:

View File

@ -19,5 +19,13 @@ repos:
hooks:
- id: build-js
name: build-js
# pre-commit pass as parameters files included on the commit
# so babel command should be wrapped to ignore these files on
# package.json script
entry: npm run babel
language: node
files: ^ereuse_devicehub/static/js/main_inventory.js
- repo: https://github.com/jazzband/pip-tools
rev: 6.8.0
hooks:
- id: pip-compile

View File

@ -7,6 +7,31 @@ ml).
## testing
## [2.4.2] - 2022-10-18
- [added] #373 Enhancement - UX Lots.
- [added] #377 add prefix in lots in device list.
- [added] #378 add new button transfer.
- [added] #381 add servers erase and show storage disk in list of device.
- [added] #383 new setup page and add server_erase in placeholder.
- [added] #384 add redirect snapshot to twin public page.
- [changed] #371 changes phid.
- [changed] #372 remove logo.
- [changed] #374 changes links UI management and Data Storage Erasure.
- [changed] #375 changes columns in snapshot logs.
- [changed] #379 changes representation date times.
- [fixed] #380 fix layout print label.
- [fixed] #382 fix template device list.
- [fixed] #385 components in unbinding process.
## [2.4.1] - 2022-10-05
- [added] #365 Manage dependencies using pip-tools.
- [added] #368 add migrations of monitors and mobiles.
- [changed]] #371 changes about phid, incremental per user.
- [fixed] #364 bad redirect to all devices.
- [fixed] #367 column PHID Erasure host.
- [fixed] #369 bug in test data storage.
- [fixed] #370 print label in details of the label.
## [2.4.0] - 2022-09-23
- [added] #312 Placeholder: new, edit, update. (manually and with excel).
- [added] #316 Placeholder: binding/unbinding. (manually).

View File

@ -30,3 +30,29 @@ pre-commit install
Do this: `device_detail.html`
Don't do this: `DeviceDetail.html`, `Device-detail.html`
## Adding a new dependency to the project
This project tracks its packages using pip-tools, it could be installed by running:
```
pip install pip-tools
```
Whenever you need to install a new package using pip install <package-name>:
1. Put the package name into `requirements.in` instead.
```
# requirements.in
...
new_package
```
2. Compile the requirements
```
pip-compile requirements.in --output-file=requirements.txt
```
3. Then install upgraded dependencies:
```
pip install -U -r requirements.txt
```

View File

@ -1 +1 @@
__version__ = "2.4.0"
__version__ = "2.4.2"

View File

@ -86,12 +86,18 @@ DEVICES = {
"Smartphone",
"Cellphone",
],
"Drives & Storage": [
"All DataStorage",
"HardDrives",
"SolidStageDrive",
],
}
COMPUTERS = ['Desktop', 'Laptop', 'Server', 'Computer']
MONITORS = ["ComputerMonitor", "Monitor", "TelevisionSet", "Projector"]
MOBILE = ["Mobile", "Tablet", "Smartphone", "Cellphone"]
STORAGE = ["HardDrive", "SolidStateDrive"]
class AdvancedSearchForm(FlaskForm):
@ -175,9 +181,16 @@ class FilterForm(FlaskForm):
elif "All Mobile" == self.device_type:
filter_type = MOBILE
elif "All DataStorage" == self.device_type:
filter_type = STORAGE
if filter_type:
self.devices = self.devices.filter(Device.type.in_(filter_type))
# if self.device_type in STORAGE + ["All DataStorage"]:
# import pdb; pdb.set_trace()
# self.devices = self.devices.filter(Component.parent_id.is_(None))
return self.devices.order_by(Device.updated.desc())
@ -322,7 +335,7 @@ class NewDeviceForm(FlaskForm):
default=1,
)
id_device_supplier = StringField('Id Supplier', [validators.Optional()])
phid = StringField('Placeholder Hardware identity (Phid)', [validators.Optional()])
id_device_internal = StringField('Id Internal', [validators.Optional()])
pallet = StringField('Identity of pallet', [validators.Optional()])
components = TextAreaField('Components', [validators.Optional()])
info = TextAreaField('Info', [validators.Optional()])
@ -382,7 +395,7 @@ class NewDeviceForm(FlaskForm):
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.id_device_internal.data = self._obj.placeholder.id_device_internal
self.pallet.data = self._obj.placeholder.pallet
self.info.data = self._obj.placeholder.info
self.components.data = self._obj.placeholder.components
@ -411,7 +424,7 @@ class NewDeviceForm(FlaskForm):
if self._obj.placeholder.is_abstract:
self.type.render_kw = disabled
self.amount.render_kw = disabled
self.id_device_supplier.render_kw = disabled
# self.id_device_supplier.render_kw = disabled
self.pallet.render_kw = disabled
self.info.render_kw = disabled
self.components.render_kw = disabled
@ -476,28 +489,6 @@ 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, Placeholder.owner == g.user
).first()
if dev:
msg = "Error, exist one Placeholder device with this PHID"
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 = "Error, exist one Placeholder device with this PHID"
self.phid.errors = [msg]
is_valid = False
if not is_valid:
return False
@ -579,8 +570,8 @@ class NewDeviceForm(FlaskForm):
def reset_ids(self):
if self.amount.data > 1:
self.phid.data = None
self.id_device_supplier.data = None
self.id_device_internal.data = None
self.serial_number.data = None
self.part_number.data = None
self.sku.data = None
@ -590,8 +581,8 @@ class NewDeviceForm(FlaskForm):
def get_placeholder(self):
self.placeholder = Placeholder(
**{
'phid': self.phid.data or None,
'id_device_supplier': self.id_device_supplier.data,
'id_device_internal': self.id_device_internal.data,
'info': self.info.data,
'components': self.components.data,
'pallet': self.pallet.data,
@ -601,11 +592,13 @@ class NewDeviceForm(FlaskForm):
return self.placeholder
def edit_device(self):
self._obj.placeholder.phid = self.phid.data or self._obj.placeholder.phid
if not self._obj.placeholder.is_abstract:
self._obj.placeholder.id_device_supplier = (
self.id_device_supplier.data or None
)
self._obj.placeholder.id_device_internal = (
self.id_device_internal.data or None
)
self._obj.placeholder.info = self.info.data or None
self._obj.placeholder.components = self.components.data or None
self._obj.placeholder.pallet = self.pallet.data or None
@ -1279,6 +1272,12 @@ class TradeDocumentForm(FlaskForm):
class TransferForm(FlaskForm):
lot_name = StringField(
'Lot Name',
[validators.DataRequired()],
render_kw={'class': "form-control"},
description="You need put a lot name",
)
code = StringField(
'Code',
[validators.DataRequired()],
@ -1295,14 +1294,14 @@ class TransferForm(FlaskForm):
def __init__(self, *args, **kwargs):
self._type = kwargs.get('type')
lot_id = kwargs.pop('lot_id', None)
self._tmp_lot = None
if lot_id:
self._tmp_lot = Lot.query.filter(Lot.id == lot_id).one()
super().__init__(*args, **kwargs)
self._obj = None
def validate(self, extra_validators=None):
is_valid = super().validate(extra_validators)
if not self._tmp_lot:
return False
if self._type and self.type.data not in ['incoming', 'outgoing']:
return False
@ -1323,7 +1322,9 @@ class TransferForm(FlaskForm):
return self._obj
def set_obj(self):
self.newlot = Lot(name=self._tmp_lot.name)
name = self.lot_name.data
self.newlot = Lot(name=name)
if self._tmp_lot:
self.newlot.devices = self._tmp_lot.devices
db.session.add(self.newlot)
@ -1355,6 +1356,7 @@ class EditTransferForm(TransferForm):
self.code.data = self._obj.code
self.description.data = self._obj.description
self.date.data = self._obj.date
self.lot_name.data = self._obj.lot.name
def validate(self, extra_validators=None):
is_valid = super().validate(extra_validators)
@ -1366,6 +1368,7 @@ class EditTransferForm(TransferForm):
def set_obj(self, commit=True):
self.populate_obj(self._obj)
self._obj.lot.name = self.lot_name.data
class NotesForm(FlaskForm):
@ -1514,9 +1517,7 @@ class UploadPlaceholderForm(FlaskForm):
else:
self.source = "Excel File: {}".format(_file.filename)
try:
data = (
pd.read_excel(_file, converters={'Phid': str}).fillna('').to_dict()
)
data = pd.read_excel(_file).fillna('').to_dict()
except ValueError:
txt = ["File don't have a correct format"]
self.placeholder_file.errors = txt
@ -1538,12 +1539,12 @@ class UploadPlaceholderForm(FlaskForm):
return False
header = [
'Phid',
'Model',
'Manufacturer',
'Serial Number',
'Part Number',
'Id device Supplier',
'Id device Internal',
'Pallet',
'Info',
]
@ -1557,32 +1558,7 @@ class UploadPlaceholderForm(FlaskForm):
self.placeholders = []
schema = SnapshotSchema()
self.path_snapshots = {}
for i in data['Phid'].keys():
placeholder = None
data['Phid'][i] = str(data['Phid'][i])
if data['Phid'][i]:
placeholder = Placeholder.query.filter_by(phid=data['Phid'][i]).first()
# update one
if placeholder:
self.dev_update += 1
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()
device.part_number = "{}".format(data['Part 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
for i in data['Model'].keys():
# create a new one
json_snapshot = {
'type': 'Snapshot',
@ -1597,8 +1573,8 @@ class UploadPlaceholderForm(FlaskForm):
},
}
json_placeholder = {
'phid': data['Phid'][i] or None,
'id_device_supplier': data['Id device Supplier'][i],
'id_device_internal': data['Id device Internal'][i],
'pallet': data['Pallet'][i],
'info': data['Info'][i],
'is_abstract': False,
@ -1635,7 +1611,6 @@ class EditPlaceholderForm(FlaskForm):
serial_number = StringField('Serial Number', [validators.Optional()])
part_number = StringField('Part 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()])

View File

@ -296,6 +296,8 @@ class BindingView(GenericMixin):
self.real_phid = self.new_placeholder.phid
self.abstract_dhid = self.old_device.devicehub_id
self.abstract_phid = self.old_placeholder.phid
if self.old_placeholder.kangaroo:
self.new_placeholder.kangaroo = True
# to do a backup of abstract_dhid and abstract_phid in
# workbench device
@ -380,9 +382,14 @@ class UnBindingView(GenericMixin):
return flask.render_template(self.template_name, **self.context)
def clone_device(self, device):
if device.binding.is_abstract:
if device.binding and device.binding.is_abstract:
return
kangaroo = False
if device.binding:
kangaroo = device.binding.kangaroo
device.binding.kangaroo = False
dict_device = copy.copy(device.__dict__)
dict_device.pop('_sa_instance_state')
dict_device.pop('id', None)
@ -401,8 +408,14 @@ class UnBindingView(GenericMixin):
for c in device.components:
if c.binding:
c.binding.device.parent = new_device
else:
new_c = self.clone_device(c)
new_c.parent = new_device
placeholder = Placeholder(
device=new_device, binding=device, is_abstract=True, kangaroo=kangaroo
)
placeholder = Placeholder(device=new_device, binding=device, is_abstract=True)
if (
device.dhid_bk
and not Device.query.filter_by(devicehub_id=device.dhid_bk).first()
@ -545,7 +558,7 @@ class DeviceCreateView(GenericMixin):
tpy = form.type.data
txt = f'{amount} placeholders Device "{tpy}" created successfully.'
placeholder = (
Placeholder.query.filter_by(owner=g.user)
Placeholder.query.filter(Placeholder.owner == g.user)
.order_by(Placeholder.id.desc())
.first()
)
@ -753,9 +766,11 @@ class NewTransferView(GenericMixin):
form_class = TransferForm
title = "Add new transfer"
def dispatch_request(self, lot_id, type_id):
def dispatch_request(self, type_id, lot_id=None):
self.form = self.form_class(lot_id=lot_id, type=type_id)
self.get_context()
referrer = request.referrer or url_for('inventory.devicelist')
self.context.update({'referrer': referrer})
if self.form.validate_on_submit():
self.form.save()
@ -767,7 +782,12 @@ class NewTransferView(GenericMixin):
next_url = url_for('inventory.lotdevicelist', lot_id=str(new_lot_id))
return flask.redirect(next_url)
self.context.update({'form': self.form, 'title': self.title})
self.context.update(
{
'form': self.form,
'title': self.title,
}
)
return flask.render_template(self.template_name, **self.context)
@ -1355,6 +1375,10 @@ devices.add_url_rule(
)
devices.add_url_rule(
'/lot/<string:lot_id>/transfer/<string:type_id>/',
view_func=NewTransferView.as_view('lot_new_transfer'),
)
devices.add_url_rule(
'/lot/transfer/<string:type_id>/',
view_func=NewTransferView.as_view('new_transfer'),
)
devices.add_url_rule(

View File

@ -114,21 +114,35 @@ class PrintLabelsView(View):
class LabelDetailView(View):
"""This View is used to print labels from multiple devices"""
methods = ['POST', 'GET']
decorators = [login_required]
template_name = 'labels/label_detail.html'
template_name = 'labels/print_labels.html'
title = 'Design and implementation of labels'
def dispatch_request(self, id):
lots = Lot.query.filter(Lot.owner_id == current_user.id)
tag = (
Tag.query.filter(Tag.owner_id == current_user.id).filter(Tag.id == id).one()
)
context = {
'lots': lots,
'tag': tag,
'page_title': '{} Tag'.format(tag.code),
'page_title': self.title,
'version': __version__,
'referrer': request.referrer,
}
devices = []
if tag.device:
form = PrintLabelsForm(devices=str(tag.device.id))
devices = [tag.device]
else:
form = PrintLabelsForm()
form._devices = devices
context['form'] = form
context['devices'] = devices
return flask.render_template(self.template_name, **context)

View File

@ -0,0 +1,59 @@
"""id internal in placeholder
Revision ID: 626c17026ca7
Revises: e919fe0611ff
Create Date: 2022-10-03 19:25:00.581699
"""
import sqlalchemy as sa
from alembic import context, op
# revision identifiers, used by Alembic.
revision = '626c17026ca7'
down_revision = 'e919fe0611ff'
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_datas():
con = op.get_bind()
sql = 'select id from common.user where phantom=false and active=true'
users = con.execute(sql)
for user in users:
phid = 1
user_id = user.id
sql = f"""
select id from {get_inv()}.placeholder where owner_id='{user_id}'
order by id
"""
placeholders = con.execute(sql)
for p in placeholders:
p_id = p.id
sql = f"""
update {get_inv()}.placeholder set phid='{phid}'
where id='{p_id}'
"""
con.execute(sql)
phid += 1
def upgrade():
op.add_column(
'placeholder',
sa.Column('id_device_internal', sa.Unicode(), nullable=True),
schema=f'{get_inv()}',
)
upgrade_datas()
def downgrade():
op.drop_column('placeholder', 'id_device_internal', schema=f'{get_inv()}')

View File

@ -0,0 +1,34 @@
"""add kangaroo in placeholder
Revision ID: a13ed6ad0e3e
Revises: 626c17026ca7
Create Date: 2022-10-13 11:56:15.303218
"""
import sqlalchemy as sa
from alembic import context, op
# revision identifiers, used by Alembic.
revision = 'a13ed6ad0e3e'
down_revision = '626c17026ca7'
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.add_column(
'placeholder',
sa.Column('kangaroo', sa.Boolean(), nullable=True),
schema=f'{get_inv()}',
)
def downgrade():
op.drop_column('placeholder', 'kangaroo', schema=f'{get_inv()}')

View File

@ -0,0 +1,34 @@
"""add is_server_erase
Revision ID: d65745749e34
Revises: a13ed6ad0e3e
Create Date: 2022-10-17 13:20:29.875274
"""
import sqlalchemy as sa
from alembic import context, op
# revision identifiers, used by Alembic.
revision = 'd65745749e34'
down_revision = 'a13ed6ad0e3e'
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.add_column(
'snapshot',
sa.Column('is_server_erase', sa.Boolean(), nullable=True),
schema=f'{get_inv()}',
)
def downgrade():
op.drop_column('snapshot', 'is_server_erase', schema=f'{get_inv()}')

View File

@ -676,6 +676,7 @@ class Snapshot(JoinedWithOneDeviceMixin, ActionWithOneDevice):
of time it took to complete.
"""
sid = Column(CIText(), nullable=True)
is_server_erase = Column(Boolean(), nullable=True)
def get_last_lifetimes(self):
"""We get the lifetime and serial_number of the first disk"""

View File

@ -296,6 +296,14 @@ class TestDataStorage(Test):
offline_uncorrectable = Integer(data_key='offlineUncorrectable')
remaining_lifetime_percentage = Integer(data_key='remainingLifetimePercentage')
@post_load
def default_remaining_lifetime_percentage(self, data):
if not data.get('remaining_lifetime_percentage'):
return
if data.get('remaining_lifetime_percentage') > 100:
data['remaining_lifetime_percentage'] = None
class StressTest(Test):
__doc__ = m.StressTest.__doc__

View File

@ -115,8 +115,15 @@ class SnapshotMixin:
if snapshot.device.hid is None:
snapshot.severity = Severity.Warning
self.is_server_erase(snapshot)
return snapshot
def is_server_erase(self, snapshot):
if snapshot.device.binding:
if snapshot.device.binding.kangaroo:
snapshot.is_server_erase = True
def get_old_smbios_version(self, debug):
capabilities = debug.get('lshw', {}).get('capabilities', {})
for x in capabilities.values():

View File

@ -10,7 +10,6 @@ from boltons import urlutils
from citext import CIText
from ereuse_utils.naming import HID_CONVERSION_DOC, Naming
from flask import g, request
from flask_sqlalchemy import event
from more_itertools import unique_everseen
from sqlalchemy import BigInteger, Boolean, Column
from sqlalchemy import Enum as DBEnum
@ -75,11 +74,15 @@ 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'
def create_phid(context, count=1):
phid = str(Placeholder.query.filter(Placeholder.owner == g.user).count() + count)
if (
Placeholder.query.filter(Placeholder.owner == g.user)
.filter(Placeholder.phid == phid)
.count()
):
return create_phid(context, count=count + 1)
return phid
class Device(Thing):
@ -650,6 +653,21 @@ class Device(Thing):
args[POLYMORPHIC_ON] = cls.type
return args
def get_lots_for_template(self):
lots = []
for lot in self.lots:
if lot.is_incoming:
name = "IN - " + lot.name
lots.append(name)
if lot.is_outgoing:
name = "OUT - " + lot.name
lots.append(name)
if lot.is_temporary:
name = "TEMP - " + lot.name
lots.append(name)
lots.sort()
return lots
def phid(self):
if self.placeholder:
return self.placeholder.phid
@ -901,8 +919,8 @@ class DisplayMixin:
class Placeholder(Thing):
id = Column(BigInteger, Sequence('placeholder_seq'), primary_key=True)
pallet = Column(Unicode(), nullable=True)
phid = Column(Unicode(), nullable=False, default=create_phid)
pallet = Column(Unicode(), nullable=True)
pallet.comment = "used for identification where from where is this placeholders"
info = db.Column(CIText())
components = Column(CIText())
@ -912,6 +930,9 @@ class Placeholder(Thing):
id_device_supplier.comment = (
"Identification used for one supplier of one placeholders"
)
id_device_internal = db.Column(CIText())
id_device_internal.comment = "Identification used internaly for the user"
kangaroo = db.Column(Boolean, default=False, nullable=True)
device_id = db.Column(
BigInteger,
@ -1593,4 +1614,5 @@ def create_code_tag(mapper, connection, device):
db.session.add(tag)
# from flask_sqlalchemy import event
# event.listen(Device, 'after_insert', create_code_tag, propagate=True)

View File

@ -137,7 +137,10 @@ class DeviceView(View):
return self.one_private(id)
def one_public(self, id: int):
device = Device.query.filter_by(devicehub_id=id, active=True).one()
devices = Device.query.filter_by(devicehub_id=id, active=True).all()
if not devices:
devices = [Device.query.filter_by(dhid_bk=id, active=True).one()]
device = devices[0]
abstract = None
if device.binding:
return flask.redirect(device.public_link)

View File

@ -26,3 +26,10 @@
.help {
color: #993365;
}
.doTransfer {
margin-top: -8px;
margin-right: 15px;"
}
.printLabelForm {
margin-left: 10px;
}

View File

@ -29,14 +29,18 @@ function amountInputs() {
if ($("#amount").val() > 1) {
$("#Phid").hide();
$("#Id_device_supplier").hide();
$("#Id_device_internal").hide();
$("#Serial_number").hide();
$("#Part_number").hide();
$("#Sku").hide();
$("#imei").hide();
$("#meid").hide();
} else {
$("#Phid").show();
$("#Id_device_supplier").show();
$("#Id_device_internal").show();
$("#Serial_number").show();
$("#Part_number").show();
$("#Sku").show();
deviceInputs();
};

View File

@ -3,11 +3,7 @@
{% block body %}
<!-- ======= Header ======= -->
<header id="header" class="header fixed-top d-flex align-items-center">
<div class="d-flex align-items-center justify-content-between">
<a href="{{ url_for('inventory.devicelist')}}" class="logo d-flex align-items-center">
<img src="{{ url_for('static', filename='img/logo_usody_clock.png') }}" alt="">
</a>
<i class="bi bi-list toggle-sidebar-btn"></i>
</div><!-- End Logo -->
@ -43,6 +39,11 @@
<i class="bi bi-search"></i>
</a>
</li><!-- End Search Icon-->
<li class="nav-item dropdown pe-3">
<a class="nav-link nav-profile d-flex align-items-center pe-0" href="{{ url_for('labels.label_list') }}">
<span class="d-none d-md-block ps-2 pb-3 pt-3">Identifiers</span>
</a>
</li>
<li class="nav-item dropdown pe-3">
@ -64,6 +65,11 @@
<span>Lots Spreadsheet</span>
</a>
</li>
<li>
<a class="dropdown-item d-flex align-items-center" href="{{ url_for('inventory.device_erasure_list') }}">
<i class="bi bi-tag"></i><span>Data Storage Erasures</span>
</a>
</li>
</ul>
</li>
@ -188,6 +194,11 @@
{% else %}
<ul id="incoming-lots-nav" class="nav-content collapse" data-bs-parent="#sidebar-nav">
{% endif %}
<li>
<a href="{{ url_for('inventory.new_transfer', type_id='incoming') }}">
<i class="bi bi-plus" style="font-size: larger;"></i><span>New Incoming lot</span>
</a>
</li>
{% for lot in lots %}
{% if lot.is_incoming %}
<li>
@ -213,6 +224,11 @@
{% else %}
<ul id="outgoing-lots-nav" class="nav-content collapse " data-bs-parent="#sidebar-nav">
{% endif %}
<li>
<a href="{{ url_for('inventory.new_transfer', type_id='outgoing') }}">
<i class="bi bi-plus" style="font-size: larger;"></i><span>New Outgoing lot</span>
</a>
</li>
{% for lot in lots %}
{% if lot.is_outgoing %}
<li>
@ -256,22 +272,6 @@
</ul>
</li><!-- End Temporal Lots Nav -->
<li class="nav-heading">Others views</li>
<li class="nav-item">
<a class="nav-link collapsed" href="{{ url_for('inventory.device_erasure_list') }}">
<i class="bi bi-tag"></i><span>Data Storage Erasures</span>
</a>
</li><!-- End Other views -->
<li class="nav-heading">Unique Identifiers (Tags)</li>
<li class="nav-item">
<a class="nav-link collapsed" href="{{ url_for('labels.label_list') }}">
<i class="bi bi-tag"></i><span>UI Management</span>
</a>
</li><!-- End Unique Identifiers -->
</ul>
</aside><!-- End Sidebar-->

View File

@ -90,19 +90,6 @@
{% endif %}
</div>
<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") }}
@ -116,6 +103,19 @@
{% endif %}
</div>
<div class="form-group mb-2" id="Id_device_internal">
<label for="label" class="form-label">{{ form.id_device_internal.label }}</label>
{{ form.id_device_internal(class_="form-control") }}
<small class="text-muted form-text">Identity of device for the internal</small>
{% if form.id_device_internal.errors %}
<p class="text-danger">
{% for error in form.id_device_internal.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") }}

View File

@ -98,6 +98,11 @@
<div class="col-lg-9 col-md-8">{{ placeholder.phid }}</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label ">Id device internal</div>
<div class="col-lg-9 col-md-8">{{ placeholder.id_device_internal or '' }}</div>
</div>
<div class="row">
<div class="col-lg-3 col-md-4 label ">Type</div>
<div class="col-lg-9 col-md-8">{{ placeholder.device.type }}</div>

View File

@ -31,8 +31,8 @@
<div class="col-xl-12">
{% if lot %}
<div class="card">
{% if lot %}
<div class="card-body pt-3">
<!-- Bordered Tabs -->
@ -41,25 +41,19 @@
<h3>
<a href="{{ url_for('inventory.lot_edit', id=lot.id) }}">{{ lot.name }}</a>
</h3>
{% if lot.transfer.code and lot.transfer.user_to and not lot.transfer.user_to.phantom %}
<span>{{ lot.transfer.code }} <i class="bi bi-arrow-right"></i> {{ lot.transfer.user_to.email }}</span>
{% elif lot.transfer.code and lot.transfer.user_from and not lot.transfer.user_from.phantom %}
<span>{{ lot.transfer.user_from.email }} <i class="bi bi-arrow-right"></i> {{ lot.transfer.code }}</span>
{% endif %}
</div>
<div class="col-sm-12 col-md-7 d-md-flex justify-content-md-end"><!-- lot actions -->
{% if lot.is_temporary or not lot.transfer.closed %}
{% if 1 == 2 %}{# <!-- TODO (@slamora) Don't render Trade buttons until implementation is finished --> #}
<a class="me-2" href="javascript:newTrade('user_from')">
<i class="bi bi-arrow-down-right"></i> Add supplier
{% if lot and lot.is_temporary %}
<a type="button" href="{{ url_for('inventory.lot_new_transfer', lot_id=lot.id, type_id='outgoing') }}" class="btn btn-primary doTransfer" >
Create Outgoing Lot
</a>
<a class="me-2" href="javascript:newTrade('user_to')">
<i class="bi bi-arrow-up-right"></i> Add receiver
<a type="button" href="{{ url_for('inventory.lot_new_transfer', lot_id=lot.id, type_id='incoming') }}" class="btn btn-primary doTransfer">
Create Incoming Lot
</a>
{% endif %}{# <!-- /end TODO --> #}
{% endif %}
<a class="text-danger" href="javascript:removeLot()">
<i class="bi bi-trash"></i> Delete Lot
</a>
@ -68,18 +62,17 @@
</div>
</div>
</div>
</div>
{% endif %}
<div class="card">
<div class="card-body pt-3" style="min-height: 650px;">
<!-- Bordered Tabs -->
{% if lot and not lot.is_temporary %}
{% if lot %}
<ul class="nav nav-tabs nav-tabs-bordered">
<li class="nav-item">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#devices-list">Devices</button>
</li>
{% if lot and not lot.is_temporary %}
<li class="nav-item">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#trade-documents-list">Documents</button>
</li>
@ -101,21 +94,11 @@
</button>
</li>
{% endif %}
{% endif %}
</ul>
{% endif %}
<div class="tab-content pt-1">
{% if lot and lot.is_temporary %}
<div class="tab-pane active show mb-5">
<a type="button" href="{{ url_for('inventory.new_transfer', lot_id=lot.id, type_id='outgoing') }}" class="btn btn-primary" style="float: right;">
Outgoing Transfer
</a>
<a type="button" href="{{ url_for('inventory.new_transfer', lot_id=lot.id, type_id='incoming') }}" class="btn btn-primary" style="float: right; margin-right: 15px;">
Incoming Transfer
</a>
<div style="display: block;"></div>
</div>
{% endif %}
<div id="devices-list" class="tab-pane fade devices-list active show">
<label class="btn btn-primary " for="SelectAllBTN"><input type="checkbox" id="SelectAllBTN" autocomplete="off"></label>
<div class="btn-group dropdown ml-1">
@ -336,24 +319,6 @@
</ul>
</div>
{% if lot and not lot.is_temporary %}
<div class="btn-group dropdown ml-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-book"></i>
Documents
</button>
<ul class="dropdown-menu" aria-labelledby="btnSnapshot">
<li>
<a href="{{ url_for('inventory.trade_document_add', lot_id=lot.id)}}" class="dropdown-item">
<i class="bi bi-plus"></i>
Add new document
<span class="caret"></span>
</a>
</li>
</ul>
</div>
{% endif %}
<div id="select-devices-info" class="alert alert-info mb-0 mt-3 d-none" role="alert">
If this text is showing is because there are an error
</div>
@ -392,6 +357,7 @@
</thead>
<tbody>
{% for dev in devices %}
{% if dev.placeholder and (not dev.parent_id or dev.parent.placeholder.kangaroo) %}
<tr>
<td>
<input type="checkbox" class="deviceSelect" data="{{ dev.id }}"
@ -411,8 +377,8 @@
</a>
{% if dev.lots | length > 0 %}
<h6 class="d-inline">
{% for lot in dev.lots %}
<span class="badge rounded-pill bg-light text-dark">{{ lot.name }}</span>
{% for lot in dev.get_lots_for_template() %}
<span class="badge rounded-pill bg-light text-dark">{{ lot }}</span>
{% endfor %}
</h6>
{% endif %}
@ -445,6 +411,7 @@
</a>
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
@ -453,12 +420,20 @@
</div>
{% if lot and not lot.is_temporary %}
<div id="trade-documents-list" class="tab-pane fade trade-documents-list">
<div class="btn-group dropdown ml-1 mt-1" uib-dropdown="">
<a href="{{ url_for('inventory.trade_document_add', lot_id=lot.id)}}" class="btn btn-primary">
<i class="bi bi-plus"></i>
Add new document
<span class="caret"></span>
</a>
</div>
<h5 class="card-title">Documents</h5>
<table class="table">
<thead>
<tr>
<th scope="col">File</th>
<th scope="col" data-type="date" data-format="DD-MM-YYYY">Uploaded on</th>
<th scope="col" data-type="date" data-format="YYYY-MM-DD hh:mm">Uploaded on</th>
</tr>
</thead>
<tbody>
@ -472,7 +447,7 @@
{% endif %}
</td>
<td>
{{ doc.created.strftime('%H:%M %d-%m-%Y')}}
{{ doc.created.strftime('%Y-%m-%d %H:%M')}}
</td>
</tr>
{% endfor %}
@ -486,7 +461,7 @@
{% endif %}
</td>
<td>
{{ doc.created.strftime('%H:%M %d-%m-%Y')}}
{{ doc.created.strftime('%Y-%m-%d %H:%M')}}
</td>
</tr>
{% endfor %}

View File

@ -38,7 +38,7 @@
<div class="col-12">
{% if field != form.type %}
{{ field.label(class_="form-label") }}
{% if field == form.code %}
{% if field in [form.code, form.lot_name] %}
<span class="text-danger">*</span>
{% endif %}
{{ field }}
@ -55,7 +55,7 @@
{% endfor %}
<div>
<a href="{{ url_for('inventory.lotdevicelist', lot_id=form._tmp_lot.id) }}" class="btn btn-danger">Cancel</a>
<a href="{{ referrer }}" class="btn btn-danger">Cancel</a>
<button class="btn btn-primary" type="submit">Save</button>
</div>
</form>

View File

@ -27,10 +27,10 @@
<tr>
<th scope="col">PHID</th>
<th scope="col">Placeholder source</th>
<th scope="col">Type</th>
<th scope="col">Type Upload</th>
<th scope="col">DHID</th>
<th scope="col">Status</th>
<th scope="col" data-type="date" data-format="DD-MM-YYYY">Time</th>
<th scope="col" data-type="date" data-format="YYYY-MM-DD hh:mm">Time</th>
</tr>
</thead>
<tbody>
@ -53,7 +53,7 @@
<td>
{{ log.get_status() }}
</td>
<td>{{ log.created.strftime('%H:%M %d-%m-%Y') }}</td>
<td>{{ log.created.strftime('%Y-%m-%d %H:%M') }}</td>
</tr>
{% endfor %}
</tbody>

View File

@ -26,7 +26,7 @@
<thead>
<tr>
<th scope="col">SID</th>
<th scope="col">Snapshot id</th>
<th scope="col">Snapshot UUID</th>
<th scope="col">Version</th>
<th scope="col">DHID</th>
<th scope="col">System UUID</th>
@ -34,7 +34,7 @@
<th scope="col">Type Upload</th>
<th scope="col">Type Device</th>
<th scope="col">Original DHID</th>
<th scope="col" data-type="date" data-format="DD-MM-YYYY">Time</th>
<th scope="col" data-type="date" data-format="YYYY-MM-DD hh:mm">Time</th>
<th scope="col"></th>
</tr>
</thead>
@ -80,7 +80,7 @@
<td>
{{ snap.original_dhid }}
</td>
<td>{{ snap.created.strftime('%H:%M %d-%m-%Y') }}</td>
<td>{{ snap.created.strftime('%Y-%m-%d %H:%M') }}</td>
<td>
{% if snap.snapshot_uuid %}
<a href="{{ url_for('inventory.export', export_id='snapshot') }}?id={{ snap.snapshot_uuid }}" target="_blank">

View File

@ -47,7 +47,7 @@
<th scope="col">Type</th>
<th scope="col">Provider</th>
<th scope="col">Device</th>
<th scope="col">Created</th>
<th scope="col" data-type="date" data-format="YYYY-MM-DD hh:mm">Created</th>
</tr>
</thead>
<tbody>
@ -63,7 +63,7 @@
</a>
{% endif %}
</td>
<td>{{ tag.created.strftime('%H:%M %d-%m-%Y') }}</td>
<td>{{ tag.created.strftime('%Y-%m-%d %H:%M') }}</td>
</tr>
{% endfor %}
</tbody>

View File

@ -13,7 +13,7 @@
<section class="section profile">
<div class="row">
<div class="col-xl-8">
<div class="col-xxl-9">
<div class="card">
<div class="card-body">
@ -91,8 +91,7 @@
</div>
{% endfor %}
</div>
<div class="col-1">
</div>
<div class="col-1 d-none d-md-block printLabelForm"></div>
<div class="col label">
<label class="col-form-label col-sm-2">Logo</label>
@ -153,7 +152,7 @@
</div>
<div class="form-switch">
<input class="form-check-input" name="tags" type="checkbox" id="tagsCheck">
<label class="form-check-label" for="tagsCheck">Tags</label>
<label class="form-check-label" for="tagsCheck">Unique Identifiers</label>
</div>
<div class="form-switch">
<input class="form-check-input" name="serial_number" type="checkbox" id="serialNumberCheck">

View File

@ -14,6 +14,62 @@
<div class="row">
<div class="col-xl-6">
<div class="card">
<div class="card-body">
<div class="pt-6 pb-2">
<div class="row pt-3">
<div class="col">
<form method="post">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">PHID</th>
<th scope="col" class="text-center">Erasure Host</th>
</tr>
</thead>
<tbody>
{% for host in form.kangaroos %}
<tr>
<td>{{ host.phid }}</td>
<td class="text-center">
<a href="{{ url_for('workbench.erasure_host', id=host.id) }}"><i class="bi bi-x-lg"></i></a>
</td>
</tr>
{% endfor %}
<tr>
<td>
{% for f in form %}
{{ f }}
{% if f == form.phid and f.errors %}
<p class="text-danger">
{% for error in f.errors %}
{{ error }}<br/>
{% endfor %}
</p>
{% endif %}
{% endfor %}
</td>
<td class="text-center">
<input type="submit" class="btn btn-primary" value="Add new host" />
</td>
</tr>
</tbody>
</table>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="card">
<div class="card-body">
@ -26,120 +82,150 @@
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-8">
</div>
</div>
{% if demo %}
<div class="row">
<div class="col-xl-6">
<div class="col">
<div class="card">
<div class="card-body">
<div class="pt-6 pb-2">
<h5 class="card-title text-center pb-0 fs-4">Workbench 2022</h5>
<div class="row pt-3">
<div class="col-5">
<a href="{{ iso.demo.url }}{{ iso.demo.iso }}" class="btn btn-primary">{{ iso.demo.iso }}</a>
<h5 class="card-title pb-0 fs-4">Usody Hardware Metadata 2022</h5>
<p class="mb-5">
A certified collection of hardware details and testing reports<br />
<small>Desktops, Servers and Laptops</small>
</p>
<div class="row">
<div class="col-2">
<a href="{{ iso_register.url }}{{ iso_register.iso }}" class="btn btn-primary" style="max-width: 200px;">
Download ISO
</a>
</div>
<div class="col">
<div class="col-3">
<p class="small">
Download Checksum: <a class="help" href="{{ iso.demo.url }}SHA512SUM">SHA512SUM</a> |
<a href="https://help.usody.com/es/setup/setup-pendrive/" target="_blank" class="help">Help</a></p>
Download Checksum: <a class="help" href="{{ iso_register.url }}SHA512SUM">SHA512SUM</a> |
<a href="https://help.usody.com/es/setup/setup-pendrive/" target="_blank" class="help">Help</a>
</p>
</div>
<div class="col"></div>
</div>
<div class="row mt-3">
<div class="col-3">
<div class="border mr-2 p-2">
<h5>Basic Metadata</h5>
<p>Settings for basic hardware metadata collection and hard drive smart Test reports.</p>
<a href="{{ url_for('workbench.settings') }}?opt=register" class="btn btn-primary" style="width: 100%">Download settings file</a>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-8">
</div>
</div>
{% else %}
{% for d, v in iso.items() %}
{% if d != 'demo' %}
</div>
</div>
{% if not demo %}
<div class="row">
<div class="col-xl-6">
<div class="col">
<div class="card">
<div class="card-body">
<div class="pt-6 pb-2">
<h5 class="card-title text-center pb-0 fs-4">Workbench {{ d }}</h5>
{% if d == 'v14' %}
<div class="pt-8 pb-2">
<h5 class="card-title pb-0 fs-4">Usody Data Erasure v14</h5>
<p class="mb-5">
A certified data erasure software to irreversibly removing data sored on hard drives.<br />
<small>Desktops, Servers and Laptops</small>
</p>
<div class="row">
<div class="col-5">
<a href="{{ url_for('workbench.settings') }}?opt=erease_basic" class="btn btn-primary" style="width: 200px;">Get settings file!</a>
<div class="col-2">
<a href="{{ iso_erease.url }}{{ iso_erease.iso }}" class="btn btn-primary" style="max-width: 200px;">
Download ISO
</a>
</div>
<div class="col">
<div class="col-3">
<p class="small">
Settings for basic data erasure. | <a href="https://help.usody.com/es/setup/setup-pendrive/" target="_blank" class="help">Help</a>
Download Checksum: <a class="help" href="{{ iso_erease.url }}SHA512SUM">SHA512SUM</a> |
<a href="https://help.usody.com/es/setup/setup-pendrive/" target="_blank" class="help">Help</a>
</p>
</div>
<div class="col"></div>
</div>
<div class="row pt-2">
<div class="col-5">
<a href="{{ url_for('workbench.settings') }}?opt=erease_sectors" class="btn btn-primary" style="width: 200px;">Get settings file!</a>
</div>
<div class="col">
<p class="small">
Settings for advanced data erasure through sectors and hidden areas. Guarantee of data removal. | <a href="https://help.usody.com/es/setup/setup-pendrive/" target="_blank" class="help">Help</a>
<div class="row mt-3">
<div class="col-3">
<div class="border mr-2 p-2">
<h5>Basic Erasure</h5>
<p>
Settings for basic data erasure using shred Linux command.
A software-based fast non-100%-secured way of erasing data storage.
</p>
</div>
</div>
{% else %}
<div class="row">
<div class="col-5">
<a href="{{ url_for('workbench.settings') }}?opt=register" class="btn btn-primary" style="width: 200px;">Get settings file!</a>
</div>
<div class="col">
<p class="small">
Settings for register devices.
<p>
Performs <strong>1</strong> pass overwriting one round using all zeros.
Compliant with <strong>NIST SP-800-88</strong>
</p>
<a href="{{ url_for('workbench.settings') }}?opt=erease_basic"
class="btn btn-primary"
style="width: 100%;">
Download settings file
</a>
</div>
</div>
{% endif %}
{% if iso %}
<br />
<div class="row">
<div class="col-5">
<a href="{{ v.url }}{{ v.iso }}" class="btn btn-primary" style="width: 200px;">Get ISO file</a>
</div>
<div class="col">
<p class="small">
{{ v.iso }}
<div class="col-3">
<div class="border mr-2 p-2">
<h5>Baseline Secure Erasure</h5>
<p>
Settings for advanced data erasure using badblocks Linux software.
A secured-way of erasing data storages, erase hidden areas, checking the erase sector by sector.
</p>
<p class="small">
Download Checksum: <a class="help" href="{{ v.url }}SHA512SUM">SHA512SUM</a> |
<a href="https://help.usody.com/es/setup/setup-pendrive/" target="_blank" class="help">Help</a></p>
<p>
Performs <strong>1</strong> pass overwriting each sector with zeros and a final verification.
Compliant with <strong>HMG Infosec Standard 5 Baseline</strong>.
</p>
<a href="{{ url_for('workbench.settings') }}?opt=erease_sectors"
class="btn btn-primary"
style="width: 100%;">
Download settings file
</a>
</div>
</div>
{% endif %}
<div class="col-3">
<div class="border mr-2 p-2">
<h5>Enhanced Secure Erasure</h5>
<p>
Settings for advanced data erasure using badblocks Linux software.
A secured-way of erasing data storages, erase hidden areas, checking the erase sector by sector.
</p>
<p>
Performs <strong>3</strong> passes overwriting every sector with zeros and ones, and final verification.
Compliant with <strong>HMG Infosec Standard 5 Enhanced</strong>.
</p>
<span
class="btn btn-secondary"
style="width: 100%;">
Download settings file
</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-8">
</div>
</div>
</div>
{% endif %}
{% endfor %}
{% endif %}
</section>
{% endblock main %}

View File

@ -3,11 +3,11 @@ isos = {
'iso': "USODY_2022.8.0-Demo.iso",
'url': 'http://releases.usody.com/demo/',
},
"2022": {
"register": {
'iso': "USODY_2022.8.0-beta.iso",
'url': 'http://releases.usody.com/2022/',
},
"v14": {
"erease": {
'iso': "USODY_14.0.0.iso",
'url': 'http://releases.usody.com/v14/',
},

View File

@ -0,0 +1,47 @@
from flask import g
from flask_wtf import FlaskForm
from wtforms import StringField, validators
from ereuse_devicehub.db import db
from ereuse_devicehub.resources.device.models import Placeholder
class KangarooForm(FlaskForm):
phid = StringField('Phid', [validators.length(min=1)])
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.placeholder = None
self.kangaroos = Placeholder.query.filter(
Placeholder.kangaroo.is_(True)
).filter(Placeholder.owner_id == g.user.id)
def validate(self, extra_validators=None):
is_valid = super().validate(extra_validators)
if not is_valid:
return False
if not self.placeholder:
self.placeholder = (
Placeholder.query.filter(Placeholder.phid == self.phid.data)
.filter(Placeholder.owner_id == g.user.id)
.first()
)
if self.placeholder:
if self.placeholder.status not in ['Snapshot', 'Twin']:
self.placeholder = None
if not self.placeholder:
self.phid.errors = ["Device not exist"]
return False
return True
def save(self):
if not self.placeholder or self.placeholder.kangaroo:
return
self.placeholder.kangaroo = True
db.session.commit()
return self.placeholder

View File

@ -3,34 +3,45 @@ import time
import flask
from flask import Blueprint
from flask import current_app as app
from flask import g, make_response, request
from flask import g, make_response, request, url_for
from flask.views import View
from flask_login import login_required
from ereuse_devicehub import auth
from ereuse_devicehub.db import db
from ereuse_devicehub.resources.device.models import Placeholder
from ereuse_devicehub.resources.enums import SessionType
from ereuse_devicehub.resources.user.models import Session
from ereuse_devicehub.views import GenericMixin
from ereuse_devicehub.workbench import isos
from ereuse_devicehub.workbench.forms import KangarooForm
workbench = Blueprint('workbench', __name__, url_prefix='/workbench')
class SettingsView(GenericMixin):
decorators = [login_required]
methods = ['GET', 'POST']
template_name = 'workbench/settings.html'
page_title = "Workbench"
page_title = "Snapshots"
def dispatch_request(self):
self.get_context()
form_kangaroo = KangarooForm()
self.context.update(
{
'page_title': self.page_title,
'demo': g.user.email == app.config['EMAIL_DEMO'],
'iso': isos,
'iso_demo': isos['demo'],
'iso_register': isos['register'],
'iso_erease': isos['erease'],
'form': form_kangaroo,
}
)
if form_kangaroo.validate_on_submit():
form_kangaroo.save()
self.opt = request.values.get('opt')
if self.opt in ['register', 'erease_basic', 'erease_sectors']:
return self.download()
@ -86,4 +97,24 @@ class SettingsView(GenericMixin):
return token
class ErasureHostView(View):
decorators = [login_required]
methods = ['GET']
def dispatch_request(self, id):
self.placeholder = (
Placeholder.query.filter(Placeholder.id == id)
.filter(Placeholder.kangaroo.is_(True))
.filter(Placeholder.owner_id == g.user.id)
.one()
)
self.placeholder.kangaroo = False
db.session.commit()
return flask.redirect(url_for('workbench.settings'))
workbench.add_url_rule('/', view_func=SettingsView.as_view('settings'))
workbench.add_url_rule(
'/erasure_host/<int:id>/', view_func=ErasureHostView.as_view('erasure_host')
)

View File

@ -17,7 +17,7 @@ from ereuse_devicehub.workbench.views import workbench
# from flask_wtf.csrf import CSRFProtect
# from werkzeug.contrib.profiler import ProfilerMiddleware
# from werkzeug.middleware.profiler import ProfilerMiddleware
SENTRY_DSN = config('SENTRY_DSN', None)

View File

@ -1,9 +1,9 @@
from ereuse_devicehub.devicehub import Devicehub
from werkzeug.contrib.profiler import ProfilerMiddleware
from werkzeug.middleware.profiler import ProfilerMiddleware
from ereuse_devicehub.devicehub import Devicehub
app = Devicehub(inventory='dbtest')
app.config["SQLALCHEMY_RECORD_QUERIES"] = True
app.config['PROFILE'] = True
app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[30])
app.run(debug = True)
app.run(debug=True)

View File

@ -21,7 +21,7 @@
"scripts": {
"lint:report": "eslint ereuse_devicehub --ext .js --output-file eslint_report.json --format json",
"lint:fix": "eslint ereuse_devicehub --ext .js --fix",
"babel": "babel ereuse_devicehub/static/js/main_inventory.js --out-file ereuse_devicehub/static/js/main_inventory.build.js"
"babel": "babel ereuse_devicehub/static/js/main_inventory.js --out-file ereuse_devicehub/static/js/main_inventory.build.js && echo 'Ignoring parameters: '"
},
"keywords": [],
"author": "",

41
requirements.in Normal file
View File

@ -0,0 +1,41 @@
alembic==1.4.2
atomicwrites==1.4.0
click-spinner==0.1.8
colorama==0.3.9
colour==0.1.5
ereuse-utils[naming,test,session,cli]==0.4.0b50
Flask-Cors==3.0.10
Flask-Login==0.5.0
Flask-WTF==1.0.0
flask-weasyprint==0.4
hashids==1.2.0
more-itertools==8.12.0
passlib==1.7.1
phonenumbers==8.9.11
psycopg2-binary==2.8.3
pyjwt==2.4.0
python-decouple==3.3
python-dotenv==0.14.0
python-stdnum==1.9
pyyaml==5.4
requests==2.27.1
requests-mock==1.5.2
requests-toolbelt==0.9.1
sortedcontainers==2.1.0
sqlalchemy-citext==1.3.post0
sqlalchemy-utils==0.33.11
teal==0.2.0a38
tqdm==4.32.2
# workbench json parsing dependencies
pint==0.9
py-dmidecode==0.1.0
pandas==1.3.5
numpy==1.22.0 # pandas dependency
odfpy==1.4.1 # pandas dependency
xlrd==2.0.1 # pandas dependency
openpyxl==3.0.10 # pandas dependency
et_xmlfile==1.1.0 # pandas dependency
# manual dependency
marshmallow-enum==1.4.1

View File

@ -1,53 +1,226 @@
#
# This file is autogenerated by pip-compile with python 3.8
# To update, run:
#
# pip-compile --output-file=requirements.txt requirements.in
#
alembic==1.4.2
# via -r requirements.in
anytree==2.4.3
# via teal
apispec==0.39.0
# via teal
atomicwrites==1.4.0
# via -r requirements.in
boltons==18.0.1
# via
# ereuse-utils
# teal
cairocffi==1.4.0
# via
# cairosvg
# weasyprint
cairosvg==2.5.2
# via weasyprint
certifi==2022.9.24
# via requests
cffi==1.15.1
# via
# cairocffi
# weasyprint
charset-normalizer==2.0.12
# via requests
click==6.7
# via
# ereuse-utils
# flask
click-spinner==0.1.8
# via
# -r requirements.in
# teal
colorama==0.3.9
# via
# -r requirements.in
# ereuse-utils
colour==0.1.5
ereuse-utils[naming,test,session,cli]==0.4.0b50
Flask==1.0.2
Flask-Cors==3.0.10
Flask-Login==0.5.0
Flask-SQLAlchemy==2.3.2
Flask-WTF==1.0.0
# via
# -r requirements.in
# sqlalchemy-utils
cssselect2==0.7.0
# via
# cairosvg
# weasyprint
defusedxml==0.7.1
# via
# cairosvg
# odfpy
ereuse-utils[cli,naming,session,test]==0.4.0b50
# via
# -r requirements.in
# teal
et-xmlfile==1.1.0
# via
# -r requirements.in
# openpyxl
flask==1.0.2
# via
# ereuse-utils
# flask-cors
# flask-login
# flask-sqlalchemy
# flask-weasyprint
# flask-wtf
# teal
flask-cors==3.0.10
# via
# -r requirements.in
# teal
flask-login==0.5.0
# via -r requirements.in
flask-sqlalchemy==2.5.1
# via teal
flask-weasyprint==0.4
# via -r requirements.in
flask-wtf==1.0.0
# via -r requirements.in
hashids==1.2.0
# via -r requirements.in
html5lib==1.1
# via weasyprint
idna==3.4
# via requests
inflection==0.3.1
# via ereuse-utils
itsdangerous==2.0.1
# lock Jinja2 version because it's the latest compatible with Flask 1.0.X
# see related info on https://github.com/pallets/jinja/issues/1628
Jinja2==3.0.3
# via
# flask
# flask-wtf
jinja2==3.0.3
# via flask
mako==1.2.3
# via alembic
markupsafe==2.1.1
# via
# jinja2
# mako
# wtforms
marshmallow==3.0.0b11
# via
# marshmallow-enum
# teal
# webargs
marshmallow-enum==1.4.1
passlib==1.7.1
phonenumbers==8.9.11
pytest==3.7.2
pytest-runner==4.2
python-dateutil==2.7.3
python-stdnum==1.9
PyYAML==5.4
requests[security]==2.27.1
requests-mock==1.5.2
SQLAlchemy==1.3.24
SQLAlchemy-Utils==0.33.11
teal==0.2.0a38
webargs==5.5.3
Werkzeug==0.15.5
sqlalchemy-citext==1.3.post0
flask-weasyprint==0.5
weasyprint==44
psycopg2-binary==2.8.3
sortedcontainers==2.1.0
tqdm==4.32.2
python-decouple==3.3
python-dotenv==0.14.0
pyjwt==2.4.0
pint==0.9
py-dmidecode==0.1.0
# via -r requirements.in
more-itertools==8.12.0
# via -r requirements.in
numpy==1.22.0
# via
# -r requirements.in
# pandas
odfpy==1.4.1
# via -r requirements.in
openpyxl==3.0.10
# via -r requirements.in
pandas==1.3.5
numpy==1.22.0 # pandas dependency
odfpy==1.4.1 # pandas dependency
xlrd==2.0.1 # pandas dependency
openpyxl==3.0.10 # pandas dependency
et_xmlfile==1.1.0 # pandas dependency
# via -r requirements.in
passlib==1.7.1
# via
# -r requirements.in
# sqlalchemy-utils
phonenumbers==8.9.11
# via
# -r requirements.in
# sqlalchemy-utils
pillow==9.2.0
# via cairosvg
pint==0.9
# via -r requirements.in
psycopg2-binary==2.8.3
# via -r requirements.in
py-dmidecode==0.1.0
# via -r requirements.in
pycparser==2.21
# via cffi
pyjwt==2.4.0
# via -r requirements.in
pyphen==0.13.0
# via weasyprint
python-dateutil==2.7.3
# via
# alembic
# pandas
python-decouple==3.3
# via -r requirements.in
python-dotenv==0.14.0
# via -r requirements.in
python-editor==1.0.4
# via alembic
python-stdnum==1.9
# via -r requirements.in
pytz==2022.2.1
# via pandas
pyyaml==5.4
# via
# -r requirements.in
# apispec
requests==2.27.1
# via
# -r requirements.in
# requests-mock
# requests-toolbelt
requests-mock==1.5.2
# via -r requirements.in
requests-toolbelt==0.9.1
# via
# -r requirements.in
# ereuse-utils
six==1.16.0
# via
# anytree
# flask-cors
# html5lib
# python-dateutil
# requests-mock
# sqlalchemy-utils
sortedcontainers==2.1.0
# via -r requirements.in
sqlalchemy==1.3.24
# via
# alembic
# flask-sqlalchemy
# sqlalchemy-citext
# sqlalchemy-utils
sqlalchemy-citext==1.3.post0
# via -r requirements.in
sqlalchemy-utils[color,password,phone]==0.33.11
# via
# -r requirements.in
# teal
teal==0.2.0a38
# via -r requirements.in
tinycss2==1.1.1
# via
# cairosvg
# cssselect2
# weasyprint
tqdm==4.32.2
# via
# -r requirements.in
# ereuse-utils
urllib3==1.26.12
# via requests
weasyprint==44
# via flask-weasyprint
webargs==5.5.3
# via teal
webencodings==0.5.1
# via
# cssselect2
# html5lib
# tinycss2
werkzeug==2.0.3
# via flask
wtforms==3.0.1
# via flask-wtf
xlrd==2.0.1
# via -r requirements.in

View File

@ -1,4 +1,4 @@
Phid;Model;Manufacturer;Serial Number;Part 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
Model;Manufacturer;Serial Number;Part Number;Id device Supplier;Id device Internal;Pallet;Info
Vaio;Sony;12345678;;TTT;AA;24A;Good conditions
Vaio;Sony;12345679;;TTT;BB;24A;Good conditions
Vaio;Sony;12345680;;TTT;CC;24A;Good conditions

1 Phid Model Manufacturer Serial Number Part Number Id device Supplier Id device Internal Pallet Info
2 a123 Vaio Sony 12345678 TTT AA 24A Good conditions
3 a124 Vaio Sony 12345679 TTT BB 24A Good conditions
4 a125 Vaio Sony 12345680 TTT CC 24A Good conditions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,4 @@
import copy
import ipaddress
import json
import os
import shutil
@ -7,7 +6,7 @@ from datetime import datetime, timedelta
from decimal import Decimal
from io import BytesIO
from json.decoder import JSONDecodeError
from typing import Tuple, Type
from typing import Tuple
import pytest
from dateutil.tz import tzutc
@ -15,7 +14,7 @@ from flask import current_app as app
from flask import g
from pytest import raises
from sqlalchemy.util import OrderedSet
from teal.enums import Currency, Subdivision
from teal.enums import Currency
from ereuse_devicehub.client import Client, UserClient
from ereuse_devicehub.db import db
@ -82,11 +81,7 @@ def test_erase_basic():
def test_validate_device_data_storage():
"""Checks the validation for data-storage-only actions works."""
# We can't set a GraphicCard
with pytest.raises(
TypeError,
message='EraseBasic.device must be a DataStorage '
'but you passed <GraphicCard None model=\'foo-bar\' S/N=\'foo\'>',
):
with pytest.raises(TypeError):
models.EraseBasic(
device=GraphicCard(
serial_number='foo', manufacturer='bar', model='foo-bar'
@ -94,6 +89,10 @@ def test_validate_device_data_storage():
clean_with_zeros=True,
**conftest.T,
)
pytest.fail(
'EraseBasic.device must be a DataStorage '
'but you passed <GraphicCard None model=\'foo-bar\' S/N=\'foo\'>'
)
@pytest.mark.mvp
@ -292,9 +291,7 @@ def test_generic_action(
for ams in [models.Recycling, models.Use, models.Refurbish, models.Management]
),
)
def test_simple_status_actions(
action_model: models.Action, user2: UserClient
):
def test_simple_status_actions(action_model: models.Action, user2: UserClient):
"""Simple test of status action."""
user = user2
snap, _ = user.post(file('basic.snapshot'), res=models.Snapshot)
@ -554,7 +551,9 @@ def test_status_without_lot(action_model: models.Action, user: UserClient):
for ams in [models.Recycling, models.Use, models.Refurbish, models.Management]
),
)
def test_status_in_temporary_lot(action_model: models.Action, user: UserClient, app: Devicehub):
def test_status_in_temporary_lot(
action_model: models.Action, user: UserClient, app: Devicehub
):
"""Test of status actions for devices in a temporary lot."""
snap, _ = user.post(file('basic.snapshot'), res=models.Snapshot)
abstract = Device.query.filter_by(id=snap['device']['id']).first()
@ -727,7 +726,7 @@ def test_live_without_TestDataStorage(user: UserClient, client: Client, app: Dev
acer = file('acer.happy.battery.snapshot')
snapshot, _ = user.post(acer, res=models.Snapshot)
device_id = snapshot['device']['id']
db_device = Device.query.filter_by(id=device_id).one()
post_request = {
"transaction": "ccc",
"name": "John",
@ -767,7 +766,7 @@ def test_live_without_hdd_1(user: UserClient, client: Client, app: Devicehub):
acer = file('acer.happy.battery.snapshot')
snapshot, _ = user.post(acer, res=models.Snapshot)
device_id = snapshot['device']['id']
db_device = Device.query.filter_by(id=device_id).one()
post_request = {
"transaction": "ccc",
"name": "John",
@ -803,7 +802,7 @@ def test_live_without_hdd_2(user: UserClient, client: Client, app: Devicehub):
acer['components'] = components
snapshot, _ = user.post(json_encode(acer), res=models.Snapshot)
device_id = snapshot['device']['id']
db_device = Device.query.filter_by(id=device_id).one()
post_request = {
"transaction": "ccc",
"name": "John",
@ -838,7 +837,7 @@ def test_live_without_hdd_3(user: UserClient, client: Client, app: Devicehub):
acer['components'] = components
snapshot, _ = user.post(json_encode(acer), res=models.Snapshot)
device_id = snapshot['device']['id']
db_device = Device.query.filter_by(id=device_id).one()
post_request = {
"transaction": "ccc",
"name": "John",
@ -875,7 +874,7 @@ def test_live_with_hdd_with_old_time(user: UserClient, client: Client, app: Devi
acer = file('acer.happy.battery.snapshot')
snapshot, _ = user.post(acer, res=models.Snapshot)
device_id = snapshot['device']['id']
db_device = Device.query.filter_by(id=device_id).one()
post_request = {
"transaction": "ccc",
"name": "John",
@ -1022,7 +1021,7 @@ def test_allocate(user: UserClient):
allocate, _ = user.post(res=models.Allocate, data=post_request)
# Normal allocate
device, _ = user.get(res=Device, item=devicehub_id)
assert device['allocated'] == True
assert device['allocated'] is True
action = [a for a in device['actions'] if a['type'] == 'Allocate'][0]
assert action['transaction'] == allocate['transaction']
assert action['finalUserCode'] == allocate['finalUserCode']
@ -1097,11 +1096,11 @@ def test_deallocate(user: UserClient):
user.post(res=models.Allocate, data=post_allocate)
device, _ = user.get(res=Device, item=devicehub_id)
assert device['allocated'] == True
assert device['allocated'] is True
deallocate, _ = user.post(res=models.Deallocate, data=post_deallocate)
assert deallocate['startTime'] == post_deallocate['startTime']
assert deallocate['devices'][0]['id'] == device_id
assert deallocate['devices'][0]['allocated'] == False
assert deallocate['devices'][0]['allocated'] is False
res, _ = user.post(res=models.Deallocate, data=post_deallocate, status=422)
assert res['code'] == 422
assert res['type'] == 'ValidationError'
@ -1204,8 +1203,8 @@ def test_offer_without_to(user: UserClient):
users = [ac.user for ac in trade.acceptances]
assert trade.user_to == device.owner
assert request_post['code'].lower() in device.owner.email
assert device.owner.active == False
assert device.owner.phantom == True
assert device.owner.active is False
assert device.owner.phantom is True
assert trade.user_to in users
assert trade.user_from in users
assert device.owner.email != user.email
@ -1283,8 +1282,8 @@ def test_offer_without_from(user: UserClient, user2: UserClient):
phantom_user = trade.user_from
assert request_post['code'].lower() in phantom_user.email
assert phantom_user.active == False
assert phantom_user.phantom == True
assert phantom_user.active is False
assert phantom_user.phantom is True
# assert trade.confirm_transfer
users = [ac.user for ac in trade.acceptances]
@ -1778,12 +1777,12 @@ def test_confirmRevoke(user: UserClient, user2: UserClient):
assert device_10 in trade.devices
assert len(trade.devices) == 10
# the SCRAP confirms the revoke action
request_confirm_revoke = {
'type': 'ConfirmRevoke',
'action': device_10.actions[-2].id,
'devices': [snap10['device']['id']],
}
# TODO??? the SCRAP confirms the revoke action
# request_confirm_revoke = {
# 'type': 'ConfirmRevoke',
# 'action': device_10.actions[-2].id,
# 'devices': [snap10['device']['id']],
# }
# check validation error
# user2.post(res=models.Action, data=request_confirm_revoke, status=422)
@ -2796,7 +2795,7 @@ def test_action_web_erase(user: UserClient, client: Client):
)
assert "alert alert-info" in response
assert "100% coincidence." in response
assert not "alert alert-danger" in response
assert "alert alert-danger" not in response
@pytest.mark.mvp
@ -2805,7 +2804,7 @@ def test_moveOnDocument(user: UserClient, user2: UserClient):
lotIn, _ = user.post({'name': 'MyLotIn'}, res=Lot)
lotOut, _ = user.post({'name': 'MyLotOut'}, res=Lot)
url = (
'http://www.ereuse.org/apapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaapaapaaaa',
'http://www.ereuse.org/apapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaap',
)
request_post1 = {
'filename': 'test.pdf',
@ -2887,7 +2886,7 @@ def test_delete_devices(user: UserClient):
assert action_delete.t == 'Delete'
assert str(action_delete.id) == action['id']
assert db_device.active == False
assert db_device.active is False
# Check use of filter from frontend
url = '/devices/?filter={"type":["Computer"]}'
@ -2953,7 +2952,6 @@ def test_delete_devices_permitions(user: UserClient, user2: UserClient):
file_snap = file('1-device-with-components.snapshot')
snap, _ = user.post(file_snap, res=models.Snapshot)
device = Device.query.filter_by(id=snap['device']['id']).one()
request = {
'type': 'Delete',
@ -2973,7 +2971,7 @@ def test_moveOnDocument_bug168(user: UserClient, user2: UserClient):
lotIn, _ = user.post({'name': 'MyLotIn'}, res=Lot)
lotOut, _ = user.post({'name': 'MyLotOut'}, res=Lot)
url = (
'http://www.ereuse.org/apapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaapaapaaaa',
'http://www.ereuse.org/apapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaap',
)
request_post1 = {
'filename': 'test.pdf',
@ -3014,13 +3012,12 @@ def test_moveOnDocument_bug168(user: UserClient, user2: UserClient):
'container_to': id_hash,
'description': description,
}
doc, _ = user.post(res=models.Action, data=request_moveOn)
trade = models.Trade.query.one()
user.post(res=models.Action, data=request_moveOn)
trade_document1 = TradeDocument.query.filter_by(id=tradedocument_from['id']).one()
trade_document2 = TradeDocument.query.filter_by(id=tradedocument_to['id']).one()
assert trade_document1.total_weight == 150.0
assert trade_document2.total_weight == 4.0
assert trade_document1.trading == 'Confirm'
assert trade_document2.trading == None
assert trade_document2.trading is None
tradedocument, _ = user.delete(res=TradeDocument, item=tradedocument_to['id'])

View File

@ -21,14 +21,22 @@ def test_authenticate_success(app: Devicehub):
def test_authenticate_error(app: Devicehub):
"""Tests the authenticate method with wrong token values."""
with app.app_context():
MESSAGE = 'Provide a suitable token.'
create_user()
# Token doesn't exist
with pytest.raises(Unauthorized, message=MESSAGE):
with pytest.raises(Unauthorized):
app.auth.authenticate(token=str(uuid4()))
pytest.fail('Provide a suitable token.')
@pytest.mark.mvp
def test_authenticate_error_malformed_token(app: Devicehub):
"""Tests the authenticate method with malformed token."""
with app.app_context():
create_user()
# Wrong token format
with pytest.raises(Unauthorized, message=MESSAGE):
with pytest.raises(Unauthorized):
app.auth.authenticate(token='this is a wrong uuid')
pytest.fail('Provide a suitable token.')
@pytest.mark.mvp
@ -36,4 +44,6 @@ def test_auth_view(user: UserClient, client: Client):
"""Tests authentication at endpoint / view."""
user.get(res='User', item=user.user['id'], status=200)
client.get(res='User', item=user.user['id'], status=Unauthorized)
client.get(res='User', item=user.user['id'], token='wrong token', status=Unauthorized)
client.get(
res='User', item=user.user['id'], token='wrong token', status=Unauthorized
)

View File

@ -68,6 +68,7 @@ def test_api_docs(client: Client):
'/inventory/lot/{lot_id}/trade-document/add/',
'/inventory/lot/{lot_id}/transfer/{type_id}/',
'/inventory/lot/{lot_id}/transfer/',
'/inventory/lot/transfer/{type_id}/',
'/inventory/lot/{lot_id}/upload-snapshot/',
'/inventory/snapshots/{snapshot_uuid}/',
'/inventory/snapshots/',
@ -104,6 +105,7 @@ def test_api_docs(client: Client):
'/users/logout/',
'/versions/',
'/workbench/',
'/workbench/erasure_host/{id}/',
}
assert docs['info'] == {'title': 'Devicehub', 'version': '0.2'}
assert docs['components']['securitySchemes']['bearerAuth'] == {

View File

@ -1,13 +1,12 @@
import copy
import datetime
import pytest
from uuid import UUID
from flask import g
import pytest
from colour import Color
from ereuse_utils.naming import Naming
from ereuse_utils.test import ANY
from flask import g
from pytest import raises
from sqlalchemy.util import OrderedSet
from teal.db import ResourceNotFound
@ -20,26 +19,35 @@ from ereuse_devicehub.resources.action import models as m
from ereuse_devicehub.resources.action.models import Remove, TestConnectivity
from ereuse_devicehub.resources.agent.models import Person
from ereuse_devicehub.resources.device import models as d
from ereuse_devicehub.resources.device.exceptions import NeedsId
from ereuse_devicehub.resources.device.schemas import Device as DeviceS
from ereuse_devicehub.resources.device.sync import MismatchBetweenTags, MismatchBetweenTagsAndHid, \
Sync
from ereuse_devicehub.resources.enums import ComputerChassis, DisplayTech, Severity, \
SnapshotSoftware, TransferState
from ereuse_devicehub.resources.device.sync import (
MismatchBetweenTags,
MismatchBetweenTagsAndHid,
Sync,
)
from ereuse_devicehub.resources.enums import (
ComputerChassis,
DisplayTech,
Severity,
SnapshotSoftware,
TransferState,
)
from ereuse_devicehub.resources.tag.model import Tag
from ereuse_devicehub.resources.user import User
from tests import conftest
from tests.conftest import file, yaml2json, json_encode
from tests.conftest import file, json_encode, yaml2json
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_device_model():
"""Tests that the correctness of the device model and its relationships."""
pc = d.Desktop(model='p1mo',
pc = d.Desktop(
model='p1mo',
manufacturer='p1ma',
serial_number='p1s',
chassis=ComputerChassis.Tower)
chassis=ComputerChassis.Tower,
)
net = d.NetworkAdapter(model='c1mo', manufacturer='c1ma', serial_number='c1s')
graphic = d.GraphicCard(model='c2mo', manufacturer='c2ma', memory=1500)
pc.components.add(net)
@ -55,7 +63,9 @@ def test_device_model():
# Removing a component from pc doesn't delete the component
pc.components.remove(net)
db.session.commit()
pc = d.Device.query.filter_by(id=pc.id).first() # this is the same as querying for d.Desktop directly
pc = d.Device.query.filter_by(
id=pc.id
).first() # this is the same as querying for d.Desktop directly
assert pc.components == {graphic}
network_adapter = d.NetworkAdapter.query.one()
assert network_adapter not in pc.components
@ -72,7 +82,9 @@ def test_device_model():
assert network_adapter.id == 4
assert d.NetworkAdapter.query.first() is not None, 'We removed the network adaptor'
assert gcard.id == 5, 'We should still hold a reference to a zombie graphic card'
assert d.GraphicCard.query.first() is None, 'We should have deleted it it was inside the pc'
assert (
d.GraphicCard.query.first() is None
), 'We should have deleted it it was inside the pc'
@pytest.mark.xfail(reason='Test not developed')
@ -92,21 +104,25 @@ def test_device_schema():
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_physical_properties():
c = d.Motherboard(slots=2,
c = d.Motherboard(
slots=2,
usb=3,
serial_number='sn',
model='ml',
manufacturer='mr',
width=2.0,
color=Color())
pc = d.Desktop(chassis=ComputerChassis.Tower,
color=Color(),
)
pc = d.Desktop(
chassis=ComputerChassis.Tower,
model='foo',
manufacturer='bar',
serial_number='foo-bar',
weight=2.8,
width=1.4,
height=2.1,
color=Color('LightSeaGreen'))
color=Color('LightSeaGreen'),
)
pc.components.add(c)
db.session.add(pc)
db.session.commit()
@ -122,7 +138,7 @@ def test_physical_properties():
'manufacturer': 'mr',
'bios_date': None,
'ram_max_size': None,
'ram_slots': None
'ram_slots': None,
}
assert pc.physical_properties == {
'chassis': ComputerChassis.Tower,
@ -132,7 +148,7 @@ def test_physical_properties():
'receiver_id': None,
'serial_number': 'foo-bar',
'part_number': None,
'transfer_state': TransferState.Initial
'transfer_state': TransferState.Initial,
}
@ -142,14 +158,19 @@ def test_component_similar_one():
user = User.query.filter().first()
snapshot = yaml2json('pc-components.db')
pc = snapshot['device']
snapshot['components'][0]['serial_number'] = snapshot['components'][1]['serial_number'] = None
pc = d.Desktop(**pc, components=OrderedSet(d.Component(**c) for c in snapshot['components']))
snapshot['components'][0]['serial_number'] = snapshot['components'][1][
'serial_number'
] = None
pc = d.Desktop(
**pc, components=OrderedSet(d.Component(**c) for c in snapshot['components'])
)
component1, component2 = pc.components # type: d.Component
db.session.add(pc)
db.session.flush()
# Let's create a new component named 'A' similar to 1
componentA = d.Component(model=component1.model, manufacturer=component1.manufacturer,
owner_id=user.id)
componentA = d.Component(
model=component1.model, manufacturer=component1.manufacturer, owner_id=user.id
)
similar_to_a = componentA.similar_one(pc, set())
assert similar_to_a == component1
# d.Component B does not have the same model
@ -175,9 +196,11 @@ def test_add_remove():
pc = d.Desktop(**pc, components=OrderedSet([c1, c2]))
db.session.add(pc)
c3 = d.Component(serial_number='nc1', owner_id=user.id)
pc2 = d.Desktop(serial_number='s2',
pc2 = d.Desktop(
serial_number='s2',
components=OrderedSet([c3]),
chassis=ComputerChassis.Microtower)
chassis=ComputerChassis.Microtower,
)
c4 = d.Component(serial_number='c4s', owner_id=user.id)
db.session.add(pc2)
db.session.add(c4)
@ -201,7 +224,9 @@ def test_sync_run_components_empty():
remove all the components from the device.
"""
s = yaml2json('pc-components.db')
pc = d.Desktop(**s['device'], components=OrderedSet(d.Component(**c) for c in s['components']))
pc = d.Desktop(
**s['device'], components=OrderedSet(d.Component(**c) for c in s['components'])
)
db.session.add(pc)
db.session.commit()
@ -219,7 +244,9 @@ def test_sync_run_components_none():
keep all the components from the device.
"""
s = yaml2json('pc-components.db')
pc = d.Desktop(**s['device'], components=OrderedSet(d.Component(**c) for c in s['components']))
pc = d.Desktop(
**s['device'], components=OrderedSet(d.Component(**c) for c in s['components'])
)
db.session.add(pc)
db.session.commit()
@ -249,7 +276,8 @@ def test_sync_execute_register_desktop_existing_no_tag():
db.session.commit()
pc = d.Desktop(
**yaml2json('pc-components.db')['device']) # Create a new transient non-db object
**yaml2json('pc-components.db')['device']
) # Create a new transient non-db object
# 1: device exists on DB
db_pc = Sync().execute_register(pc)
pc.amount = 0
@ -285,7 +313,9 @@ def test_sync_execute_register_desktop_tag_not_linked():
db.session.commit()
# Create a new transient non-db object
pc = d.Desktop(**yaml2json('pc-components.db')['device'], tags=OrderedSet([Tag(id='foo')]))
pc = d.Desktop(
**yaml2json('pc-components.db')['device'], tags=OrderedSet([Tag(id='foo')])
)
returned_pc = Sync().execute_register(pc)
assert returned_pc == pc
assert tag.device == pc, 'Tag has to be linked'
@ -326,7 +356,9 @@ def test_sync_execute_register_tag_does_not_exist():
Tags have to be created before trying to link them through a Snapshot.
"""
user = User.query.filter().first()
pc = d.Desktop(**yaml2json('pc-components.db')['device'], tags=OrderedSet([Tag('foo')]))
pc = d.Desktop(
**yaml2json('pc-components.db')['device'], tags=OrderedSet([Tag('foo')])
)
pc.owner_id = user.id
with raises(ResourceNotFound):
Sync().execute_register(pc)
@ -345,7 +377,8 @@ def test_sync_execute_register_tag_linked_same_device():
db.session.commit()
pc = d.Desktop(
**yaml2json('pc-components.db')['device']) # Create a new transient non-db object
**yaml2json('pc-components.db')['device']
) # Create a new transient non-db object
pc.tags.add(Tag(id='foo'))
db_pc = Sync().execute_register(pc)
assert db_pc.id == orig_pc.id
@ -369,7 +402,8 @@ def test_sync_execute_register_tag_linked_other_device_mismatch_between_tags():
db.session.commit()
pc1 = d.Desktop(
**yaml2json('pc-components.db')['device']) # Create a new transient non-db object
**yaml2json('pc-components.db')['device']
) # Create a new transient non-db object
pc1.tags.add(Tag(id='foo-1'))
pc1.tags.add(Tag(id='foo-2'))
with raises(MismatchBetweenTags):
@ -393,7 +427,8 @@ def test_sync_execute_register_mismatch_between_tags_and_hid():
db.session.commit()
pc1 = d.Desktop(
**yaml2json('pc-components.db')['device']) # Create a new transient non-db object
**yaml2json('pc-components.db')['device']
) # Create a new transient non-db object
pc1.tags.add(Tag(id='foo-2'))
with raises(MismatchBetweenTagsAndHid):
Sync().execute_register(pc1)
@ -404,22 +439,36 @@ def test_sync_execute_register_mismatch_between_tags_and_hid():
def test_get_device(user: UserClient):
"""Checks GETting a d.Desktop with its components."""
g.user = User.query.one()
pc = d.Desktop(model='p1mo',
pc = d.Desktop(
model='p1mo',
manufacturer='p1ma',
serial_number='p1s',
chassis=ComputerChassis.Tower,
owner_id=user.user['id'])
pc.components = OrderedSet([
d.NetworkAdapter(model='c1mo', manufacturer='c1ma', serial_number='c1s',
owner_id=user.user['id']),
d.GraphicCard(model='c2mo', manufacturer='c2ma', memory=1500, owner_id=user.user['id'])
])
owner_id=user.user['id'],
)
pc.components = OrderedSet(
[
d.NetworkAdapter(
model='c1mo',
manufacturer='c1ma',
serial_number='c1s',
owner_id=user.user['id'],
),
d.GraphicCard(
model='c2mo', manufacturer='c2ma', memory=1500, owner_id=user.user['id']
),
]
)
db.session.add(pc)
# todo test is an abstract class. replace with another one
db.session.add(TestConnectivity(device=pc,
db.session.add(
TestConnectivity(
device=pc,
severity=Severity.Info,
agent=Person(name='Timmy'),
author=User(email='bar@bar.com')))
author=User(email='bar@bar.com'),
)
)
db.session.commit()
pc_api, _ = user.get(res=d.Device, item=pc.devicehub_id)
assert len(pc_api['actions']) == 1
@ -427,10 +476,14 @@ def test_get_device(user: UserClient):
assert pc_api['actions'][0]['device'] == pc.id
assert pc_api['actions'][0]['severity'] == 'Info'
assert UUID(pc_api['actions'][0]['author'])
assert 'actions_components' not in pc_api, 'actions_components are internal use only'
assert (
'actions_components' not in pc_api
), 'actions_components are internal use only'
assert 'actions_one' not in pc_api, 'they are internal use only'
assert 'author' not in pc_api
assert tuple(c['id'] for c in pc_api['components']) == tuple(c.id for c in pc.components)
assert tuple(c['id'] for c in pc_api['components']) == tuple(
c.id for c in pc.components
)
assert pc_api['hid'] == 'desktop-p1ma-p1mo-p1s'
assert pc_api['model'] == 'p1mo'
assert pc_api['manufacturer'] == 'p1ma'
@ -443,41 +496,59 @@ def test_get_device(user: UserClient):
def test_get_devices(app: Devicehub, user: UserClient):
"""Checks GETting multiple devices."""
g.user = User.query.one()
pc = d.Desktop(model='p1mo',
pc = d.Desktop(
model='p1mo',
manufacturer='p1ma',
serial_number='p1s',
chassis=ComputerChassis.Tower,
owner_id=user.user['id'])
pc.components = OrderedSet([
d.NetworkAdapter(model='c1mo', manufacturer='c1ma', serial_number='c1s',
owner_id=user.user['id']),
d.GraphicCard(model='c2mo', manufacturer='c2ma', memory=1500,
owner_id=user.user['id'])
])
pc1 = d.Desktop(model='p2mo',
owner_id=user.user['id'],
)
pc.components = OrderedSet(
[
d.NetworkAdapter(
model='c1mo',
manufacturer='c1ma',
serial_number='c1s',
owner_id=user.user['id'],
),
d.GraphicCard(
model='c2mo', manufacturer='c2ma', memory=1500, owner_id=user.user['id']
),
]
)
pc1 = d.Desktop(
model='p2mo',
manufacturer='p2ma',
serial_number='p2s',
chassis=ComputerChassis.Tower,
owner_id=user.user['id'])
pc2 = d.Laptop(model='p3mo',
owner_id=user.user['id'],
)
pc2 = d.Laptop(
model='p3mo',
manufacturer='p3ma',
serial_number='p3s',
chassis=ComputerChassis.Netbook,
owner_id=user.user['id'])
owner_id=user.user['id'],
)
db.session.add_all((pc, pc1, pc2))
db.session.commit()
devices, _ = user.get(res=d.Device)
ids = (pc.id, pc1.id, pc2.id, pc.components[0].id, pc.components[1].id)
assert tuple(dev['id'] for dev in devices['items']) == ids
assert tuple(dev['type'] for dev in devices['items']) == (
d.Desktop.t, d.Desktop.t, d.Laptop.t, d.NetworkAdapter.t, d.GraphicCard.t
d.Desktop.t,
d.Desktop.t,
d.Laptop.t,
d.NetworkAdapter.t,
d.GraphicCard.t,
)
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_get_device_permissions(app: Devicehub, user: UserClient, user2: UserClient,
client: Client):
def test_get_device_permissions(
app: Devicehub, user: UserClient, user2: UserClient, client: Client
):
"""Checks GETting a d.Desktop with its components."""
s, _ = user.post(file('asus-eee-1000h.snapshot.11'), res=m.Snapshot)
@ -529,12 +600,12 @@ def test_get_devices_unassigned(user: UserClient):
assert len(devices['items']) == 2
from ereuse_devicehub.resources.lot.models import Lot
device_id = devices['items'][0]['id']
my_lot, _ = user.post(({'name': 'My_lot'}), res=Lot)
lot, _ = user.post({},
res=Lot,
item='{}/devices'.format(my_lot['id']),
query=[('id', device_id)])
lot, _ = user.post(
{}, res=Lot, item='{}/devices'.format(my_lot['id']), query=[('id', device_id)]
)
lot = Lot.query.filter_by(id=lot['id']).one()
assert next(iter(lot.devices)).id == device_id
@ -554,13 +625,15 @@ def test_get_devices_unassigned(user: UserClient):
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_computer_monitor():
m = d.ComputerMonitor(technology=DisplayTech.LCD,
m = d.ComputerMonitor(
technology=DisplayTech.LCD,
manufacturer='foo',
model='bar',
serial_number='foo-bar',
resolution_width=1920,
resolution_height=1080,
size=14.5)
size=14.5,
)
db.session.add(m)
db.session.commit()
@ -568,9 +641,11 @@ def test_computer_monitor():
@pytest.mark.mvp
def test_manufacturer(user: UserClient):
m, r = user.get(res='Manufacturer', query=[('search', 'asus')])
assert m == {'items': [{'name': 'Asus', 'url': 'https://en.wikipedia.org/wiki/Asus'}]}
assert m == {
'items': [{'name': 'Asus', 'url': 'https://en.wikipedia.org/wiki/Asus'}]
}
assert r.cache_control.public
assert r.expires > datetime.datetime.now()
assert r.expires.timestamp() > datetime.datetime.now().timestamp()
@pytest.mark.mvp
@ -591,12 +666,20 @@ def test_device_properties_format(app: Devicehub, user: UserClient):
assert format(pc, 's') == '(asustek computer inc.) S/N 94OAAQ021116'
assert pc.ram_size == 1024
assert pc.data_storage_size == 152627
assert pc.graphic_card_model == 'mobile 945gse express integrated graphics controller'
assert (
pc.graphic_card_model
== 'mobile 945gse express integrated graphics controller'
)
assert pc.processor_model == 'intel atom cpu n270 @ 1.60ghz'
net = next(c for c in pc.components if isinstance(c, d.NetworkAdapter))
assert format(net) == 'NetworkAdapter 5: model ar8121/ar8113/ar8114 ' \
assert (
format(net) == 'NetworkAdapter 5: model ar8121/ar8113/ar8114 '
'gigabit or fast ethernet, S/N 00:24:8c:7f:cf:2d'
assert format(net, 't') == 'NetworkAdapter ar8121/ar8113/ar8114 gigabit or fast ethernet'
)
assert (
format(net, 't')
== 'NetworkAdapter ar8121/ar8113/ar8114 gigabit or fast ethernet'
)
assert format(net, 's') == 'qualcomm atheros 00:24:8C:7F:CF:2D 100 Mbps'
hdd = next(c for c in pc.components if isinstance(c, d.DataStorage))
assert format(hdd) == 'HardDrive 10: model st9160310as, S/N 5sv4tqa6'
@ -638,8 +721,12 @@ def test_networking_model(user: UserClient):
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_cooking_mixer(user: UserClient):
mixer = d.Mixer(serial_number='foo', model='bar', manufacturer='foobar',
owner_id=user.user['id'])
mixer = d.Mixer(
serial_number='foo',
model='bar',
manufacturer='foobar',
owner_id=user.user['id'],
)
db.session.add(mixer)
db.session.commit()
@ -652,12 +739,12 @@ def test_cooking_mixer_api(user: UserClient):
'serialNumber': 'foo',
'model': 'bar',
'manufacturer': 'foobar',
'type': 'Mixer'
'type': 'Mixer',
},
'version': '11.0',
'software': SnapshotSoftware.Web.name
'software': SnapshotSoftware.Web.name,
},
res=m.Snapshot
res=m.Snapshot,
)
mixer, _ = user.get(res=d.Device, item=snapshot['device']['id'])
assert mixer['type'] == 'Mixer'
@ -673,14 +760,19 @@ def test_hid_with_mac(app: Devicehub, user: UserClient):
pc, _ = user.get(res=d.Device, item=snap['device']['devicehubID'])
assert pc['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116'
pc = d.Device.query.filter_by(devicehub_id=snap['device']['devicehubID']).one()
assert pc.placeholder.binding.hid == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d'
assert (
pc.placeholder.binding.hid
== 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d'
)
@pytest.mark.mvp
def test_hid_without_mac(app: Devicehub, user: UserClient):
"""Checks hid without mac."""
snapshot = yaml2json('asus-eee-1000h.snapshot.11')
snapshot['components'] = [c for c in snapshot['components'] if c['type'] != 'NetworkAdapter']
snapshot['components'] = [
c for c in snapshot['components'] if c['type'] != 'NetworkAdapter'
]
snap, _ = user.post(json_encode(snapshot), res=m.Snapshot)
pc, _ = user.get(res=d.Device, item=snap['device']['devicehubID'])
assert pc['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116'
@ -709,7 +801,10 @@ def test_hid_with_2networkadapters(app: Devicehub, user: UserClient):
devices, _ = user.get(res=d.Device)
laptop = devices['items'][0]
assert laptop['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d'
assert (
laptop['hid']
== 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d'
)
assert len([c for c in devices['items'] if c['type'] == 'Laptop']) == 2
@ -726,14 +821,20 @@ def test_hid_with_2network_and_drop_no_mac_in_hid(app: Devicehub, user: UserClie
pc, _ = user.get(res=d.Device, item=snap['device']['devicehubID'])
assert pc['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116'
pc = d.Device.query.filter_by(devicehub_id=snap['device']['devicehubID']).one()
assert pc.placeholder.binding.hid == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d'
assert (
pc.placeholder.binding.hid
== 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d'
)
snapshot['uuid'] = 'd1b70cb8-8929-4f36-99b7-fe052cec0abb'
snapshot['components'] = [c for c in snapshot['components'] if c != network]
user.post(json_encode(snapshot), res=m.Snapshot)
devices, _ = user.get(res=d.Device)
laptop = devices['items'][0]
assert pc.placeholder.binding.hid == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d'
assert (
pc.placeholder.binding.hid
== 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d'
)
assert len([c for c in devices['items'] if c['type'] == 'Laptop']) == 2
assert len([c for c in laptop['components'] if c['type'] == 'NetworkAdapter']) == 1
@ -752,7 +853,10 @@ def test_hid_with_2network_and_drop_mac_in_hid(app: Devicehub, user: UserClient)
pc, _ = user.get(res=d.Device, item=snap['device']['devicehubID'])
assert pc['hid'] == 'laptop-asustek_computer_inc-1000h-94oaaq021116'
pc = d.Device.query.filter_by(devicehub_id=snap['device']['devicehubID']).one()
assert pc.placeholder.binding.hid == 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d'
assert (
pc.placeholder.binding.hid
== 'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d'
)
# we drop the network card then is used for to build the hid
snapshot['uuid'] = 'd1b70cb8-8929-4f36-99b7-fe052cec0abb'
@ -762,19 +866,25 @@ def test_hid_with_2network_and_drop_mac_in_hid(app: Devicehub, user: UserClient)
laptops = [c for c in devices['items'] if c['type'] == 'Laptop']
assert len(laptops) == 4
hids = [laptops[0]['hid'], laptops[2]['hid']]
proof_hid = ['laptop-asustek_computer_inc-1000h-94oaaq021116-a0:24:8c:7f:cf:2d',
'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d']
proof_hid = [
'laptop-asustek_computer_inc-1000h-94oaaq021116-a0:24:8c:7f:cf:2d',
'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d',
]
assert all([h in proof_hid for h in hids])
# we drop all network cards
snapshot['uuid'] = 'd1b70cb8-8929-4f36-99b7-fe052cec0abc'
snapshot['components'] = [c for c in snapshot['components'] if c not in [network, network2]]
snapshot['components'] = [
c for c in snapshot['components'] if c not in [network, network2]
]
user.post(json_encode(snapshot), res=m.Snapshot)
devices, _ = user.get(res=d.Device)
laptops = [c for c in devices['items'] if c['type'] == 'Laptop']
assert len(laptops) == 4
hids = [laptops[0]['hid'], laptops[2]['hid']]
proof_hid = ['laptop-asustek_computer_inc-1000h-94oaaq021116-a0:24:8c:7f:cf:2d',
proof_hid = [
'laptop-asustek_computer_inc-1000h-94oaaq021116-a0:24:8c:7f:cf:2d',
'laptop-asustek_computer_inc-1000h-94oaaq021116-00:24:8c:7f:cf:2d',
'laptop-asustek_computer_inc-1000h-94oaaq021116']
'laptop-asustek_computer_inc-1000h-94oaaq021116',
]
assert all([h in proof_hid for h in hids])

View File

@ -21,10 +21,12 @@ from tests.conftest import TestConfig
class NoExcCliRunner(click.testing.CliRunner):
"""Runner that interfaces with the Devicehub CLI."""
def invoke(self, *args, input=None, env=None, catch_exceptions=False, color=False,
**extra):
r = super().invoke(ereuse_devicehub.cli.cli,
args, input, env, catch_exceptions, color, **extra)
def invoke(
self, *args, input=None, env=None, catch_exceptions=False, color=False, **extra
):
r = super().invoke(
ereuse_devicehub.cli.cli, args, input, env, catch_exceptions, color, **extra
)
assert r.exit_code == 0, 'CLI code {}: {}'.format(r.exit_code, r.output)
return r
@ -69,16 +71,26 @@ def test_inventory_create_delete_user(cli, tdb1, tdb2):
"""
# Create first DB
cli.inv('tdb1')
cli.invoke('inv', 'add',
'-n', 'Test DB1',
'-on', 'ACME DB1',
'-oi', 'acme-id',
'-tu', 'https://example.com',
'-tt', '3c66a6ad-22de-4db6-ac46-d8982522ec40',
'--common')
cli.invoke(
'inv',
'add',
'-n',
'Test DB1',
'-on',
'ACME DB1',
'-oi',
'acme-id',
'-tu',
'https://example.com',
'-tt',
'3c66a6ad-22de-4db6-ac46-d8982522ec40',
'--common',
)
# Create an user for first DB
cli.invoke('user', 'add', 'foo@foo.com', '-a', 'Foo', '-c', 'ES', '-p', 'Such password')
cli.invoke(
'user', 'add', 'foo@foo.com', '-a', 'Foo', '-c', 'ES', '-p', 'Such password'
)
with tdb1.app_context():
# There is a row for the inventory
@ -98,12 +110,20 @@ def test_inventory_create_delete_user(cli, tdb1, tdb2):
cli.inv('tdb2')
# Create a second DB
# Note how we don't create common anymore
cli.invoke('inv', 'add',
'-n', 'Test DB2',
'-on', 'ACME DB2',
'-oi', 'acme-id-2',
'-tu', 'https://example.com',
'-tt', 'fbad1c08-ffdc-4a61-be49-464962c186a8')
cli.invoke(
'inv',
'add',
'-n',
'Test DB2',
'-on',
'ACME DB2',
'-oi',
'acme-id-2',
'-tu',
'https://example.com',
'-tt',
'fbad1c08-ffdc-4a61-be49-464962c186a8',
)
# Create an user for with access for both DB
cli.invoke('user', 'add', 'bar@bar.com', '-a', 'Bar', '-p', 'Wow password')
@ -144,5 +164,6 @@ def test_create_existing_inventory(cli, tdb1):
cli.invoke('inv', 'add', '--common')
with tdb1.app_context():
assert db.has_schema('tdb1')
with pytest.raises(AssertionError, message='Schema tdb1 already exists.'):
with pytest.raises(AssertionError):
cli.invoke('inv', 'add', '--common')
pytest.fail('Schema tdb1 already exists.')

View File

@ -356,7 +356,6 @@ def test_label_details(user3: UserClientFlask):
user3.post(uri, data=data)
body, status = user3.get('/labels/tag1/')
assert "tag1" in body
assert "Print Label" in body
@ -562,7 +561,6 @@ def test_update_monitor(user3: UserClientFlask):
data = {
'csrf_token': generate_csrf(),
'type': "Monitor",
'phid': '1',
'serial_number': "AAAAB",
'model': "LCD 43 b",
'manufacturer': "Samsung",
@ -575,8 +573,9 @@ def test_update_monitor(user3: UserClientFlask):
}
body, status = user3.post(uri, data=data)
assert status == '200 OK'
assert 'Error, exist one Placeholder device with this PHID' in body
dev = Device.query.one()
# assert 'Error, exist one Placeholder device with this PHID' in body
dev = Device.query.all()[0]
assert Device.query.count() == 2
assert dev.type == 'Monitor'
assert dev.placeholder.id_device_supplier == "b2"
assert dev.hid == 'monitor-samsung-lc27t55-aaaab'
@ -597,7 +596,6 @@ def test_add_2_monitor(user3: UserClientFlask):
data = {
'csrf_token': generate_csrf(),
'type': "Monitor",
'phid': "AAB",
'serial_number': "AAAAB",
'model': "LC27T55",
'manufacturer': "Samsung",
@ -619,7 +617,7 @@ def test_add_2_monitor(user3: UserClientFlask):
assert typ == 'Monitor'
assert dev.placeholder.id_device_supplier == "b1"
assert dev.hid == 'monitor-samsung-lc27t55-aaaab'
assert phid == 'AAB'
assert phid == '1'
assert dhid == 'O48N2'
assert dev.model == 'lc27t55'
assert dev.placeholder.pallet == "l34"
@ -737,35 +735,6 @@ def test_add_with_ammount_laptops(user3: UserClientFlask):
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 'Error, exist one Placeholder device with this PHID' in body
assert Device.query.count() == 1
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_filter_monitor(user3: UserClientFlask):
@ -1405,6 +1374,7 @@ def test_wb_settings_register(user3: UserClientFlask):
def test_create_transfer(user3: UserClientFlask):
user3.get('/inventory/lot/add/')
lot_name = 'lot1'
lot_name2 = 'lot2'
data = {
'name': lot_name,
'csrf_token': generate_csrf(),
@ -1421,13 +1391,14 @@ def test_create_transfer(user3: UserClientFlask):
assert 'Description' in body
assert 'Save' in body
data = {'csrf_token': generate_csrf(), 'code': 'AAA'}
data = {'csrf_token': generate_csrf(), 'code': 'AAA', 'lot_name': lot_name2}
body, status = user3.post(uri, data=data)
assert status == '200 OK'
assert 'Transfer created successfully!' in body
assert 'Delete Lot' in body
assert 'Incoming Lot' in body
assert lot_name2 in body
@pytest.mark.mvp
@ -1436,6 +1407,8 @@ def test_edit_transfer(user3: UserClientFlask):
# create lot
user3.get('/inventory/lot/add/')
lot_name = 'lot1'
lot_name2 = 'lot2'
lot_name3 = 'lot3'
data = {
'name': lot_name,
'csrf_token': generate_csrf(),
@ -1453,12 +1426,13 @@ def test_edit_transfer(user3: UserClientFlask):
# create new incoming lot
uri = f'/inventory/lot/{lot_id}/transfer/incoming/'
data = {'csrf_token': generate_csrf(), 'code': 'AAA'}
data = {'csrf_token': generate_csrf(), 'code': 'AAA', 'lot_name': lot_name2}
body, status = user3.post(uri, data=data)
assert 'Transfer (<span class="text-success">Open</span>)' in body
assert '<i class="bi bi-trash"></i> Delete Lot' in body
lot = Lot.query.filter()[1]
assert lot.transfer is not None
assert lot_name2 in body
# edit transfer with errors
lot_id = lot.id
@ -1467,6 +1441,7 @@ def test_edit_transfer(user3: UserClientFlask):
'csrf_token': generate_csrf(),
'code': 'AAA',
'description': 'one one one',
'lot_name': lot_name3,
'date': datetime.datetime.now().date() + datetime.timedelta(15),
}
body, status = user3.post(uri, data=data)
@ -1481,6 +1456,7 @@ def test_edit_transfer(user3: UserClientFlask):
'csrf_token': generate_csrf(),
'code': 'AAA',
'description': 'one one one',
'lot_name': lot_name3,
'date': datetime.datetime.now().date() - datetime.timedelta(15),
}
body, status = user3.post(uri, data=data)
@ -1489,6 +1465,7 @@ def test_edit_transfer(user3: UserClientFlask):
assert 'one one one' in body
assert '<i class="bi bi-trash"></i> Delete Lot' not in body
assert 'Transfer (<span class="text-danger">Closed</span>)' in body
assert lot_name3 in body
@pytest.mark.mvp
@ -1507,7 +1484,7 @@ def test_edit_deliverynote(user3: UserClientFlask):
# create new incoming lot
uri = f'/inventory/lot/{lot_id}/transfer/incoming/'
data = {'csrf_token': generate_csrf(), 'code': 'AAA'}
data = {'csrf_token': generate_csrf(), 'code': 'AAA', 'lot_name': lot_name}
user3.post(uri, data=data)
lot = Lot.query.filter()[1]
lot_id = lot.id
@ -1548,7 +1525,7 @@ def test_edit_receivernote(user3: UserClientFlask):
# create new incoming lot
uri = f'/inventory/lot/{lot_id}/transfer/incoming/'
data = {'csrf_token': generate_csrf(), 'code': 'AAA'}
data = {'csrf_token': generate_csrf(), 'code': 'AAA', 'lot_name': lot_name}
user3.post(uri, data=data)
lot = Lot.query.filter()[1]
lot_id = lot.id
@ -1589,7 +1566,7 @@ def test_edit_notes_with_closed_transfer(user3: UserClientFlask):
# create new incoming lot
uri = f'/inventory/lot/{lot_id}/transfer/incoming/'
data = {'csrf_token': generate_csrf(), 'code': 'AAA'}
data = {'csrf_token': generate_csrf(), 'code': 'AAA', 'lot_name': lot_name}
user3.post(uri, data=data)
lot = Lot.query.filter()[1]
lot_id = lot.id
@ -1698,6 +1675,7 @@ def test_export_lots(user3: UserClientFlask):
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_export_snapshot_json(user3: UserClientFlask):
# ??
file_name = 'real-eee-1001pxd.snapshot.13.json'
snap = create_device(user3, file_name)
@ -1729,7 +1707,7 @@ def test_add_placeholder_excel(user3: UserClientFlask):
assert Device.query.count() == 3
dev = Device.query.first()
assert dev.hid == 'laptop-sony-vaio-12345678'
assert dev.placeholder.phid == 'a123'
assert dev.placeholder.phid == '1'
assert dev.placeholder.info == 'Good conditions'
assert dev.placeholder.pallet == '24A'
assert dev.placeholder.id_device_supplier == 'TTT'
@ -1755,7 +1733,7 @@ def test_add_placeholder_csv(user3: UserClientFlask):
assert Device.query.count() == 3
dev = Device.query.first()
assert dev.hid == 'laptop-sony-vaio-12345678'
assert dev.placeholder.phid == 'a123'
assert dev.placeholder.phid == '1'
assert dev.placeholder.info == 'Good conditions'
assert dev.placeholder.pallet == '24A'
assert dev.placeholder.id_device_supplier == 'TTT'
@ -1781,7 +1759,7 @@ def test_add_placeholder_ods(user3: UserClientFlask):
assert Device.query.count() == 3
dev = Device.query.first()
assert dev.hid == 'laptop-sony-vaio-12345678'
assert dev.placeholder.phid == 'a123'
assert dev.placeholder.phid == '1'
assert dev.placeholder.info == 'Good conditions'
assert dev.placeholder.pallet == '24A'
assert dev.placeholder.id_device_supplier == 'TTT'
@ -1809,10 +1787,11 @@ def test_add_placeholder_office_open_xml(user3: UserClientFlask):
assert Device.query.count() == 3
dev = Device.query.first()
assert dev.hid == 'laptop-sony-vaio-12345678'
assert dev.placeholder.phid == 'a123'
assert dev.placeholder.phid == '1'
assert dev.placeholder.info == 'Good conditions'
assert dev.placeholder.pallet == '24A'
assert dev.placeholder.id_device_supplier == 'TTT'
assert dev.placeholder.id_device_internal == 'AA'
@pytest.mark.mvp
@ -1921,8 +1900,8 @@ def test_placeholder_log_manual_edit(user3: UserClientFlask):
data = {
'csrf_token': generate_csrf(),
'type': "Laptop",
'phid': 'ace',
'serial_number': "AAAAB",
'part_number': "AAAAB",
'model': "LC27T55",
'manufacturer': "Samsung",
'generation': 1,
@ -1930,9 +1909,13 @@ def test_placeholder_log_manual_edit(user3: UserClientFlask):
'height': 0.1,
'depth': 0.1,
'id_device_supplier': "b2",
'id_device_internal': "b2i",
}
user3.post(uri, data=data)
dev = Device.query.one()
plz = Placeholder.query.first()
assert plz.id_device_supplier == "b2"
assert plz.id_device_internal == "b2i"
uri = '/inventory/device/edit/{}/'.format(dev.devicehub_id)
user3.get(uri)
@ -1948,16 +1931,20 @@ def test_placeholder_log_manual_edit(user3: UserClientFlask):
'height': 0.1,
'depth': 0.1,
'id_device_supplier': "a2",
'id_device_internal': "a2i",
}
user3.post(uri, data=data)
plz = Placeholder.query.first()
assert plz.id_device_supplier == "a2"
assert plz.id_device_internal == "a2i"
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 "Web form" in body
assert "1" in body
assert dev.devicehub_id in body
assert "" in body
assert "CSV" not in body
@ -1980,7 +1967,7 @@ def test_placeholder_log_excel_new(user3: UserClientFlask):
}
user3.post(uri, data=data, content_type="multipart/form-data")
dev = Device.query.first()
assert dev.placeholder.phid == 'a123'
assert dev.placeholder.phid == '1'
uri = '/inventory/placeholder-logs/'
body, status = user3.get(uri)
@ -1989,7 +1976,6 @@ def test_placeholder_log_excel_new(user3: UserClientFlask):
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
@ -2023,7 +2009,7 @@ def test_placeholder_log_excel_update(user3: UserClientFlask):
user3.post(uri, data=data, content_type="multipart/form-data")
dev = Device.query.first()
assert dev.placeholder.phid == 'a123'
assert dev.placeholder.phid == '1'
uri = '/inventory/placeholder-logs/'
body, status = user3.get(uri)
@ -2032,7 +2018,6 @@ def test_placeholder_log_excel_update(user3: UserClientFlask):
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
@ -2070,7 +2055,7 @@ def test_add_placeholder_excel_from_lot(user3: UserClientFlask):
assert Device.query.count() == 3
dev = Device.query.first()
assert dev.hid == 'laptop-sony-vaio-12345678'
assert dev.placeholder.phid == 'a123'
assert dev.placeholder.phid == '1'
assert dev.placeholder.info == 'Good conditions'
assert dev.placeholder.pallet == '24A'
assert dev.placeholder.id_device_supplier == 'TTT'
@ -2097,7 +2082,6 @@ def test_add_new_placeholder_from_lot(user3: UserClientFlask):
data = {
'csrf_token': generate_csrf(),
'type': "Laptop",
'phid': 'ace',
'serial_number': "AAAAB",
'model': "LC27T55",
'manufacturer': "Samsung",
@ -2110,7 +2094,7 @@ def test_add_new_placeholder_from_lot(user3: UserClientFlask):
user3.post(uri, data=data)
dev = Device.query.one()
assert dev.hid == 'laptop-samsung-lc27t55-aaaab'
assert dev.placeholder.phid == 'ace'
assert dev.placeholder.phid == '1'
assert len(lot.devices) == 1
@ -2124,7 +2108,6 @@ def test_manual_binding(user3: UserClientFlask):
data = {
'csrf_token': generate_csrf(),
'type': "Laptop",
'phid': 'sid',
'serial_number': "AAAAB",
'model': "LC27T55",
'manufacturer': "Samsung",
@ -2136,7 +2119,7 @@ def test_manual_binding(user3: UserClientFlask):
user3.post(uri, data=data)
dev = Device.query.one()
assert dev.hid == 'laptop-samsung-lc27t55-aaaab'
assert dev.placeholder.phid == 'sid'
assert dev.placeholder.phid == '1'
assert dev.placeholder.is_abstract is False
# add device from wb
@ -2155,7 +2138,7 @@ def test_manual_binding(user3: UserClientFlask):
# page binding
dhid = dev_wb.dhid
uri = f'/inventory/binding/{dhid}/sid/'
uri = f'/inventory/binding/{dhid}/1/'
body, status = user3.get(uri)
assert status == '200 OK'
assert 'sid' in body
@ -2174,11 +2157,18 @@ def test_manual_binding(user3: UserClientFlask):
assert txt in body
# check new structure
assert dev_wb.binding.phid == 'sid'
assert dev_wb.binding.phid == '1'
assert dev_wb.binding.device == dev
assert dev_wb.phid() == dev.phid()
assert dev_wb.is_abstract() == dev.is_abstract() == 'Twin'
# assert dev_wb.
assert Placeholder.query.filter_by(id=old_placeholder.id).first() is None
assert Device.query.filter_by(id=old_placeholder.device.id).first() is None
body_real, status = user3.get(f'/devices/{dhid_real}')
body_abstract, status = user3.get(f'/devices/{dhid_abstract}')
assert body_real == body_abstract
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
@ -2237,7 +2227,6 @@ def test_unbinding(user3: UserClientFlask):
data = {
'csrf_token': generate_csrf(),
'type': "Laptop",
'phid': 'sid',
'serial_number': "AAAAB",
'model': "LC27T55",
'manufacturer': "Samsung",
@ -2262,20 +2251,22 @@ def test_unbinding(user3: UserClientFlask):
# page binding
dhid = dev_wb.dhid
uri = f'/inventory/binding/{dhid}/sid/'
uri = f'/inventory/binding/{dhid}/1/'
user3.get(uri)
# action binding
assert Placeholder.query.count() == 11
assert dev.placeholder.binding is None
user3.post(uri, data={})
assert dev.placeholder.binding == dev_wb
assert Placeholder.query.count() == 1
dhid = dev.dhid
# action unbinding
uri = '/inventory/unbinding/sid/'
uri = '/inventory/unbinding/1/'
body, status = user3.post(uri, data={})
assert status == '200 OK'
txt = f'Device with PHID:&#34;sid&#34; and DHID: {dhid} unbind successfully!'
txt = f'Device with PHID:&#34;1&#34; and DHID: {dhid} unbind successfully!'
assert txt in body
# assert 'Device &#34;sid&#34; unbind successfully!' in body
@ -2292,6 +2283,7 @@ def test_unbinding(user3: UserClientFlask):
assert Device.query.filter_by(id=dev_wb.binding.device.id).first()
assert Device.query.filter_by(id=dev.id).first()
assert Placeholder.query.filter_by(id=dev.placeholder.id).first()
assert Placeholder.query.count() == 11
@pytest.mark.mvp
@ -2400,8 +2392,8 @@ def test_bug_3821_binding(user3: UserClientFlask):
user3.post(uri, data=data)
dev = Device.query.one()
dhid = dev.dhid
assert dev.phid() == 'sid'
uri = f'/inventory/binding/{dhid}/sid/'
assert dev.phid() == '1'
uri = f'/inventory/binding/{dhid}/1/'
body, status = user3.get(uri)
assert status == '200 OK'
assert 'is not a Snapshot device!' in body
@ -2430,7 +2422,7 @@ def test_bug_3831_documents(user3: UserClientFlask):
uri = f'/inventory/lot/{lot_id}/transfer/incoming/'
user3.get(uri)
data = {'csrf_token': generate_csrf(), 'code': 'AAA'}
data = {'csrf_token': generate_csrf(), 'code': 'AAA', 'lot_name': lot_name}
body, status = user3.post(uri, data=data)
assert status == '200 OK'
@ -2457,3 +2449,132 @@ def test_bug_3831_documents(user3: UserClientFlask):
uri = f'/inventory/lot/{lot_id}/trade-document/add/'
# body, status = user3.post(uri, data=data, content_type="multipart/form-data")
# assert status == '200 OK'
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_hdd_filter(user3: UserClientFlask):
create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
hdds = Device.query.filter_by(type='HardDrive').all()
for hdd in hdds:
hdd.parent = None
db.session.commit()
csrf = generate_csrf()
uri = f'/inventory/device/?filter=All+DataStorage&csrf_token={csrf}'
body, status = user3.get(uri)
assert status == '200 OK'
for hdd in hdds:
assert hdd.dhid in body
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_add_kangaroo(user3: UserClientFlask):
create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
body, status = user3.get('/workbench/')
assert status == '200 OK'
pc = Device.query.filter_by(type='Laptop').first()
data = {
'csrf_token': generate_csrf(),
'phid': pc.phid(),
}
body, status = user3.post('/workbench/', data=data)
assert status == '200 OK'
assert pc.phid() in body
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_drop_kangaroo(user3: UserClientFlask):
create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
pc = Device.query.filter_by(type='Laptop').first()
phid = 'AAA'
pc.placeholder.phid = phid
db.session.commit()
body, status = user3.get('/workbench/')
assert phid not in body
data = {
'csrf_token': generate_csrf(),
'phid': phid,
}
body, status = user3.post('/workbench/', data=data)
assert status == '200 OK'
assert phid in body
placeholder_id = pc.placeholder.id
uri = f'/workbench/erasure_host/{placeholder_id}/'
body, status = user3.get(uri)
assert status == '200 OK'
assert phid not in body
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_filter_hdd_in_kangaroo(user3: UserClientFlask):
create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
csrf = generate_csrf()
uri = f'/inventory/device/?filter=All+DataStorage&csrf_token={csrf}'
body, status = user3.get(uri)
assert status == '200 OK'
for hdd in Device.query.filter_by(type='HardDrive').all():
assert hdd.dhid not in body
user3.get('/workbench/')
pc = Device.query.filter_by(type='Laptop').first()
data = {
'csrf_token': generate_csrf(),
'phid': pc.phid(),
}
user3.post('/workbench/', data=data)
csrf = generate_csrf()
uri = f'/inventory/device/?filter=All+DataStorage&csrf_token={csrf}'
body, status = user3.get(uri)
assert status == '200 OK'
for hdd in Device.query.filter_by(type='HardDrive').all():
assert hdd.dhid in body
@pytest.mark.mvp
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_snapshot_is_server_erase(user3: UserClientFlask):
snapshot = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
user3.get('/workbench/')
data = {
'csrf_token': generate_csrf(),
'phid': snapshot.device.phid(),
}
user3.post('/workbench/', data=data)
uri = '/inventory/upload-snapshot/'
file_name = 'real-eee-1001pxd.snapshot.12'
snapshot_json = conftest.yaml2json(file_name)
snapshot_json['uuid'] = 'c058e8d2-fb92-47cb-a4b7-522b75561136'
b_snapshot = bytes(json.dumps(snapshot_json), 'utf-8')
file_snap = (BytesIO(b_snapshot), file_name)
user3.get(uri)
data = {
'snapshot': file_snap,
'csrf_token': generate_csrf(),
}
user3.post(uri, data=data, content_type="multipart/form-data")
snapshot2 = Snapshot.query.filter_by(uuid=snapshot_json['uuid']).one()
assert not snapshot.is_server_erase
assert snapshot2.is_server_erase
assert snapshot in snapshot.device.actions
assert snapshot2 in snapshot.device.actions