Merge branch 'feature/server-side-render' into feature/server-side-render-UX-improvements
This commit is contained in:
commit
b6cf1f1e3e
|
@ -4,35 +4,39 @@ from json.decoder import JSONDecodeError
|
|||
from requests.exceptions import ConnectionError
|
||||
|
||||
from boltons.urlutils import URL
|
||||
from flask import g, request
|
||||
from flask_wtf import FlaskForm
|
||||
from sqlalchemy.util import OrderedSet
|
||||
from wtforms import (
|
||||
BooleanField, DateField, FileField, FloatField, Form, HiddenField,
|
||||
IntegerField, MultipleFileField, SelectField, StringField, TextAreaField,
|
||||
URLField, validators)
|
||||
from wtforms.fields import FormField
|
||||
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.resources.action.models import RateComputer, Snapshot
|
||||
from ereuse_devicehub.resources.action.rate.v1_0 import CannotRate
|
||||
from ereuse_devicehub.resources.action.schemas import \
|
||||
Snapshot as SnapshotSchema
|
||||
from ereuse_devicehub.resources.action.views.snapshot import move_json, save_json
|
||||
from ereuse_devicehub.resources.device.models import (SAI, Cellphone, Computer,
|
||||
Device, Keyboard, MemoryCardReader,
|
||||
Monitor, Mouse, Smartphone, Tablet)
|
||||
from flask import g, request
|
||||
from flask_wtf import FlaskForm
|
||||
from sqlalchemy.util import OrderedSet
|
||||
from wtforms import (BooleanField, DateField, FileField, FloatField, Form,
|
||||
HiddenField, IntegerField, MultipleFileField, SelectField,
|
||||
StringField, TextAreaField, URLField, validators)
|
||||
from wtforms.fields import FormField
|
||||
|
||||
from ereuse_devicehub.resources.action.views.snapshot import (
|
||||
move_json, save_json)
|
||||
from ereuse_devicehub.resources.device.models import (
|
||||
SAI, Cellphone, Computer, Device, Keyboard, MemoryCardReader, Monitor,
|
||||
Mouse, Smartphone, Tablet)
|
||||
from ereuse_devicehub.resources.device.sync import Sync
|
||||
from ereuse_devicehub.resources.documents.models import DataWipeDocument
|
||||
from ereuse_devicehub.resources.enums import Severity, SnapshotSoftware
|
||||
from ereuse_devicehub.resources.hash_reports import insert_hash
|
||||
from ereuse_devicehub.resources.lot.models import Lot
|
||||
from ereuse_devicehub.resources.tag.model import Tag
|
||||
from ereuse_devicehub.resources.tradedocument.models import TradeDocument
|
||||
from ereuse_devicehub.resources.user.exceptions import InsufficientPermission
|
||||
from ereuse_devicehub.resources.user.models import User
|
||||
|
||||
|
||||
class LotDeviceForm(FlaskForm):
|
||||
lot = StringField(u'Lot', [validators.UUID()])
|
||||
devices = StringField(u'Devices', [validators.length(min=1)])
|
||||
lot = StringField('Lot', [validators.UUID()])
|
||||
devices = StringField('Devices', [validators.length(min=1)])
|
||||
|
||||
def validate(self, extra_validators=None):
|
||||
is_valid = super().validate(extra_validators)
|
||||
|
@ -40,16 +44,29 @@ class LotDeviceForm(FlaskForm):
|
|||
if not is_valid:
|
||||
return False
|
||||
|
||||
self._lot = Lot.query.filter(Lot.id == self.lot.data).filter(
|
||||
Lot.owner_id == g.user.id).one()
|
||||
self._lot = (
|
||||
Lot.query.filter(Lot.id == self.lot.data)
|
||||
.filter(Lot.owner_id == g.user.id)
|
||||
.one()
|
||||
)
|
||||
|
||||
devices = set(self.devices.data.split(","))
|
||||
self._devices = Device.query.filter(Device.id.in_(devices)).filter(
|
||||
Device.owner_id == g.user.id).distinct().all()
|
||||
self._devices = (
|
||||
Device.query.filter(Device.id.in_(devices))
|
||||
.filter(Device.owner_id == g.user.id)
|
||||
.distinct()
|
||||
.all()
|
||||
)
|
||||
|
||||
return bool(self._devices)
|
||||
|
||||
def save(self):
|
||||
trade = self._lot.trade
|
||||
if trade:
|
||||
for dev in self._devices:
|
||||
if trade not in dev.actions:
|
||||
trade.devices.add(dev)
|
||||
|
||||
self._lot.devices.update(self._devices)
|
||||
db.session.add(self._lot)
|
||||
db.session.commit()
|
||||
|
@ -61,14 +78,17 @@ class LotDeviceForm(FlaskForm):
|
|||
|
||||
|
||||
class LotForm(FlaskForm):
|
||||
name = StringField(u'Name', [validators.length(min=1)])
|
||||
name = StringField('Name', [validators.length(min=1)])
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.id = kwargs.pop('id', None)
|
||||
self.instance = None
|
||||
if self.id:
|
||||
self.instance = Lot.query.filter(Lot.id == self.id).filter(
|
||||
Lot.owner_id == g.user.id).one()
|
||||
self.instance = (
|
||||
Lot.query.filter(Lot.id == self.id)
|
||||
.filter(Lot.owner_id == g.user.id)
|
||||
.one()
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.instance and not self.name.data:
|
||||
self.name.data = self.instance.name
|
||||
|
@ -96,7 +116,7 @@ class LotForm(FlaskForm):
|
|||
|
||||
|
||||
class UploadSnapshotForm(FlaskForm):
|
||||
snapshot = MultipleFileField(u'Select a Snapshot File', [validators.DataRequired()])
|
||||
snapshot = MultipleFileField('Select a Snapshot File', [validators.DataRequired()])
|
||||
|
||||
def validate(self, extra_validators=None):
|
||||
is_valid = super().validate(extra_validators)
|
||||
|
@ -164,8 +184,10 @@ class UploadSnapshotForm(FlaskForm):
|
|||
# this is a copy adaptated from ereuse_devicehub.resources.action.views.snapshot
|
||||
device = snapshot_json.pop('device') # type: Computer
|
||||
components = None
|
||||
if snapshot_json['software'] == (SnapshotSoftware.Workbench or SnapshotSoftware.WorkbenchAndroid):
|
||||
components = snapshot_json.pop('components', None) # type: List[Component]
|
||||
if snapshot_json['software'] == (
|
||||
SnapshotSoftware.Workbench or SnapshotSoftware.WorkbenchAndroid
|
||||
):
|
||||
components = snapshot_json.pop('components', None)
|
||||
if isinstance(device, Computer) and device.hid:
|
||||
device.add_mac_to_hid(components_snap=components)
|
||||
snapshot = Snapshot(**snapshot_json)
|
||||
|
@ -174,7 +196,9 @@ class UploadSnapshotForm(FlaskForm):
|
|||
actions_device = set(e for e in device.actions_one)
|
||||
device.actions_one.clear()
|
||||
if components:
|
||||
actions_components = tuple(set(e for e in c.actions_one) for c in components)
|
||||
actions_components = tuple(
|
||||
set(e for e in c.actions_one) for c in components
|
||||
)
|
||||
for component in components:
|
||||
component.actions_one.clear()
|
||||
|
||||
|
@ -220,38 +244,40 @@ class UploadSnapshotForm(FlaskForm):
|
|||
|
||||
|
||||
class NewDeviceForm(FlaskForm):
|
||||
type = StringField(u'Type', [validators.DataRequired()])
|
||||
label = StringField(u'Label')
|
||||
serial_number = StringField(u'Seria Number', [validators.DataRequired()])
|
||||
model = StringField(u'Model', [validators.DataRequired()])
|
||||
manufacturer = StringField(u'Manufacturer', [validators.DataRequired()])
|
||||
appearance = StringField(u'Appearance', [validators.Optional()])
|
||||
functionality = StringField(u'Functionality', [validators.Optional()])
|
||||
brand = StringField(u'Brand')
|
||||
generation = IntegerField(u'Generation')
|
||||
version = StringField(u'Version')
|
||||
weight = FloatField(u'Weight', [validators.DataRequired()])
|
||||
width = FloatField(u'Width', [validators.DataRequired()])
|
||||
height = FloatField(u'Height', [validators.DataRequired()])
|
||||
depth = FloatField(u'Depth', [validators.DataRequired()])
|
||||
variant = StringField(u'Variant', [validators.Optional()])
|
||||
sku = StringField(u'SKU', [validators.Optional()])
|
||||
image = StringField(u'Image', [validators.Optional(), validators.URL()])
|
||||
imei = IntegerField(u'IMEI', [validators.Optional()])
|
||||
meid = StringField(u'MEID', [validators.Optional()])
|
||||
resolution = IntegerField(u'Resolution width', [validators.Optional()])
|
||||
screen = FloatField(u'Screen size', [validators.Optional()])
|
||||
type = StringField('Type', [validators.DataRequired()])
|
||||
label = StringField('Label')
|
||||
serial_number = StringField('Seria Number', [validators.DataRequired()])
|
||||
model = StringField('Model', [validators.DataRequired()])
|
||||
manufacturer = StringField('Manufacturer', [validators.DataRequired()])
|
||||
appearance = StringField('Appearance', [validators.Optional()])
|
||||
functionality = StringField('Functionality', [validators.Optional()])
|
||||
brand = StringField('Brand')
|
||||
generation = IntegerField('Generation')
|
||||
version = StringField('Version')
|
||||
weight = FloatField('Weight', [validators.DataRequired()])
|
||||
width = FloatField('Width', [validators.DataRequired()])
|
||||
height = FloatField('Height', [validators.DataRequired()])
|
||||
depth = FloatField('Depth', [validators.DataRequired()])
|
||||
variant = StringField('Variant', [validators.Optional()])
|
||||
sku = StringField('SKU', [validators.Optional()])
|
||||
image = StringField('Image', [validators.Optional(), validators.URL()])
|
||||
imei = IntegerField('IMEI', [validators.Optional()])
|
||||
meid = StringField('MEID', [validators.Optional()])
|
||||
resolution = IntegerField('Resolution width', [validators.Optional()])
|
||||
screen = FloatField('Screen size', [validators.Optional()])
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.devices = {"Smartphone": Smartphone,
|
||||
self.devices = {
|
||||
"Smartphone": Smartphone,
|
||||
"Tablet": Tablet,
|
||||
"Cellphone": Cellphone,
|
||||
"Monitor": Monitor,
|
||||
"Mouse": Mouse,
|
||||
"Keyboard": Keyboard,
|
||||
"SAI": SAI,
|
||||
"MemoryCardReader": MemoryCardReader}
|
||||
"MemoryCardReader": MemoryCardReader,
|
||||
}
|
||||
|
||||
if not self.generation.data:
|
||||
self.generation.data = 1
|
||||
|
@ -341,16 +367,18 @@ class NewDeviceForm(FlaskForm):
|
|||
'height': self.height.data,
|
||||
'depth': self.depth.data,
|
||||
'variant': self.variant.data,
|
||||
'image': self.image.data
|
||||
}
|
||||
'image': self.image.data,
|
||||
},
|
||||
}
|
||||
|
||||
if self.appearance.data or self.functionality.data:
|
||||
json_snapshot['device']['actions'] = [{
|
||||
json_snapshot['device']['actions'] = [
|
||||
{
|
||||
'type': 'VisualTest',
|
||||
'appearanceRange': self.appearance.data,
|
||||
'functionalityRange': self.functionality.data
|
||||
}]
|
||||
'functionalityRange': self.functionality.data,
|
||||
}
|
||||
]
|
||||
|
||||
upload_form = UploadSnapshotForm()
|
||||
upload_form.sync = Sync()
|
||||
|
@ -380,14 +408,14 @@ class NewDeviceForm(FlaskForm):
|
|||
|
||||
|
||||
class TagForm(FlaskForm):
|
||||
code = StringField(u'Code', [validators.length(min=1)])
|
||||
code = StringField('Code', [validators.length(min=1)])
|
||||
|
||||
def validate(self, extra_validators=None):
|
||||
error = ["This value is being used"]
|
||||
is_valid = super().validate(extra_validators)
|
||||
if not is_valid:
|
||||
return False
|
||||
tag = Tag.query.filter(Tag.id==self.code.data).all()
|
||||
tag = Tag.query.filter(Tag.id == self.code.data).all()
|
||||
if tag:
|
||||
self.code.errors = error
|
||||
return False
|
||||
|
@ -408,7 +436,7 @@ class TagForm(FlaskForm):
|
|||
|
||||
|
||||
class TagUnnamedForm(FlaskForm):
|
||||
amount = IntegerField(u'amount')
|
||||
amount = IntegerField('amount')
|
||||
|
||||
def save(self):
|
||||
num = self.amount.data
|
||||
|
@ -424,8 +452,8 @@ class TagUnnamedForm(FlaskForm):
|
|||
|
||||
|
||||
class TagDeviceForm(FlaskForm):
|
||||
tag = SelectField(u'Tag', choices=[])
|
||||
device = StringField(u'Device', [validators.Optional()])
|
||||
tag = SelectField('Tag', choices=[])
|
||||
device = StringField('Device', [validators.Optional()])
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.delete = kwargs.pop('delete', None)
|
||||
|
@ -434,9 +462,11 @@ class TagDeviceForm(FlaskForm):
|
|||
super().__init__(*args, **kwargs)
|
||||
|
||||
if self.delete:
|
||||
tags = Tag.query.filter(Tag.owner_id==g.user.id).filter(Tag.device_id==self.device_id)
|
||||
tags = Tag.query.filter(Tag.owner_id == g.user.id).filter_by(
|
||||
device_id=self.device_id
|
||||
)
|
||||
else:
|
||||
tags = Tag.query.filter(Tag.owner_id==g.user.id).filter(Tag.device_id==None)
|
||||
tags = Tag.query.filter(Tag.owner_id == g.user.id).filter_by(device_id=None)
|
||||
|
||||
self.tag.choices = [(tag.id, tag.id) for tag in tags]
|
||||
|
||||
|
@ -446,8 +476,11 @@ class TagDeviceForm(FlaskForm):
|
|||
if not is_valid:
|
||||
return False
|
||||
|
||||
self._tag = Tag.query.filter(Tag.id == self.tag.data).filter(
|
||||
Tag.owner_id == g.user.id).one()
|
||||
self._tag = (
|
||||
Tag.query.filter(Tag.id == self.tag.data)
|
||||
.filter(Tag.owner_id == g.user.id)
|
||||
.one()
|
||||
)
|
||||
|
||||
if not self.delete and self._tag.device_id:
|
||||
self.tag.errors = [("This tag is actualy in use.")]
|
||||
|
@ -461,8 +494,11 @@ class TagDeviceForm(FlaskForm):
|
|||
|
||||
if self.device_id or self.device.data:
|
||||
self.device_id = self.device_id or self.device.data
|
||||
self._device = Device.query.filter(Device.id == self.device_id).filter(
|
||||
Device.owner_id == g.user.id).one()
|
||||
self._device = (
|
||||
Device.query.filter(Device.id == self.device_id)
|
||||
.filter(Device.owner_id == g.user.id)
|
||||
.one()
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
@ -478,38 +514,55 @@ class TagDeviceForm(FlaskForm):
|
|||
|
||||
|
||||
class NewActionForm(FlaskForm):
|
||||
name = StringField(u'Name', [validators.length(max=50)],
|
||||
description="A name or title of the event. Something to look for.")
|
||||
name = StringField(
|
||||
'Name',
|
||||
[validators.length(max=50)],
|
||||
description="A name or title of the event. Something to look for.",
|
||||
)
|
||||
devices = HiddenField()
|
||||
date = DateField(u'Date', [validators.Optional()],
|
||||
date = DateField(
|
||||
'Date',
|
||||
[validators.Optional()],
|
||||
description="""When the action ends. For some actions like booking
|
||||
the time when it expires, for others like renting the
|
||||
time that the end rents. For specific actions, it is the
|
||||
time in which they are carried out; differs from created
|
||||
in that created is where the system receives the action.""")
|
||||
severity = SelectField(u'Severity', choices=[(v.name, v.name) for v in Severity],
|
||||
in that created is where the system receives the action.""",
|
||||
)
|
||||
severity = SelectField(
|
||||
'Severity',
|
||||
choices=[(v.name, v.name) for v in Severity],
|
||||
description="""An indicator that evaluates the execution of the event.
|
||||
For example, failed events are set to Error""")
|
||||
description = TextAreaField(u'Description')
|
||||
For example, failed events are set to Error""",
|
||||
)
|
||||
description = TextAreaField('Description')
|
||||
lot = HiddenField()
|
||||
type = HiddenField()
|
||||
|
||||
def validate(self, extra_validators=None):
|
||||
is_valid = super().validate(extra_validators)
|
||||
is_valid = self.generic_validation(extra_validators=extra_validators)
|
||||
|
||||
if not is_valid:
|
||||
return False
|
||||
|
||||
self._devices = OrderedSet()
|
||||
if self.devices.data:
|
||||
devices = set(self.devices.data.split(","))
|
||||
self._devices = OrderedSet(Device.query.filter(Device.id.in_(devices)).filter(
|
||||
Device.owner_id == g.user.id).all())
|
||||
self._devices = OrderedSet(
|
||||
Device.query.filter(Device.id.in_(devices))
|
||||
.filter(Device.owner_id == g.user.id)
|
||||
.all()
|
||||
)
|
||||
|
||||
if not self._devices:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def generic_validation(self, extra_validators=None):
|
||||
# Some times we want check validations without devices list
|
||||
return super().validate(extra_validators)
|
||||
|
||||
def save(self):
|
||||
Model = db.Model._decl_class_registry.data[self.type.data]()
|
||||
self.instance = Model()
|
||||
|
@ -527,13 +580,20 @@ class NewActionForm(FlaskForm):
|
|||
|
||||
return self.instance
|
||||
|
||||
def check_valid(self):
|
||||
if self.type.data in ['', None]:
|
||||
return
|
||||
|
||||
if not self.validate():
|
||||
return self.type.data
|
||||
|
||||
|
||||
class AllocateForm(NewActionForm):
|
||||
start_time = DateField(u'Start time')
|
||||
end_time = DateField(u'End time')
|
||||
final_user_code = StringField(u'Final user code', [validators.length(max=50)])
|
||||
transaction = StringField(u'Transaction', [validators.length(max=50)])
|
||||
end_users = IntegerField(u'End users')
|
||||
start_time = DateField('Start time')
|
||||
end_time = DateField('End time')
|
||||
final_user_code = StringField('Final user code', [validators.length(max=50)])
|
||||
transaction = StringField('Transaction', [validators.length(max=50)])
|
||||
end_users = IntegerField('End users')
|
||||
|
||||
def validate(self, extra_validators=None):
|
||||
is_valid = super().validate(extra_validators)
|
||||
|
@ -554,19 +614,31 @@ class AllocateForm(NewActionForm):
|
|||
|
||||
|
||||
class DataWipeDocumentForm(Form):
|
||||
date = DateField(u'Date', [validators.Optional()],
|
||||
description="Date when was data wipe")
|
||||
url = URLField(u'Url', [validators.Optional()],
|
||||
description="Url where the document resides")
|
||||
success = BooleanField(u'Success', [validators.Optional()],
|
||||
description="The erase was success or not?")
|
||||
software = StringField(u'Software', [validators.Optional()],
|
||||
description="Which software has you use for erase the disks")
|
||||
id_document = StringField(u'Document Id', [validators.Optional()],
|
||||
description="Identification number of document")
|
||||
file_name = FileField(u'File', [validators.DataRequired()],
|
||||
date = DateField(
|
||||
'Date', [validators.Optional()], description="Date when was data wipe"
|
||||
)
|
||||
url = URLField(
|
||||
'Url', [validators.Optional()], description="Url where the document resides"
|
||||
)
|
||||
success = BooleanField(
|
||||
'Success', [validators.Optional()], description="The erase was success or not?"
|
||||
)
|
||||
software = StringField(
|
||||
'Software',
|
||||
[validators.Optional()],
|
||||
description="Which software has you use for erase the disks",
|
||||
)
|
||||
id_document = StringField(
|
||||
'Document Id',
|
||||
[validators.Optional()],
|
||||
description="Identification number of document",
|
||||
)
|
||||
file_name = FileField(
|
||||
'File',
|
||||
[validators.DataRequired()],
|
||||
description="""This file is not stored on our servers, it is only used to
|
||||
generate a digital signature and obtain the name of the file.""")
|
||||
generate a digital signature and obtain the name of the file.""",
|
||||
)
|
||||
|
||||
def validate(self, extra_validators=None):
|
||||
is_valid = super().validate(extra_validators)
|
||||
|
@ -619,3 +691,230 @@ class DataWipeForm(NewActionForm):
|
|||
self.document = document
|
||||
|
||||
return self.instance
|
||||
|
||||
|
||||
class TradeForm(NewActionForm):
|
||||
user_from = StringField(
|
||||
'Supplier',
|
||||
[validators.Optional()],
|
||||
description="Please enter the supplier's email address",
|
||||
render_kw={'data-email': ""},
|
||||
)
|
||||
user_to = StringField(
|
||||
'Receiver',
|
||||
[validators.Optional()],
|
||||
description="Please enter the receiver's email address",
|
||||
render_kw={'data-email': ""},
|
||||
)
|
||||
confirm = BooleanField(
|
||||
'Confirm',
|
||||
[validators.Optional()],
|
||||
default=True,
|
||||
description="I need confirmation from the other user for every device and document.",
|
||||
)
|
||||
code = StringField(
|
||||
'Code',
|
||||
[validators.Optional()],
|
||||
description="If you don't need confirm, you need put a code for trace the user in the statistics.",
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.user_from.render_kw['data-email'] = g.user.email
|
||||
self.user_to.render_kw['data-email'] = g.user.email
|
||||
self._lot = (
|
||||
Lot.query.filter(Lot.id == self.lot.data)
|
||||
.filter(Lot.owner_id == g.user.id)
|
||||
.one()
|
||||
)
|
||||
|
||||
def validate(self, extra_validators=None):
|
||||
is_valid = self.generic_validation(extra_validators=extra_validators)
|
||||
email_from = self.user_from.data
|
||||
email_to = self.user_to.data
|
||||
|
||||
if not self.confirm.data and not self.code.data:
|
||||
self.code.errors = ["If you don't want to confirm, you need a code"]
|
||||
is_valid = False
|
||||
|
||||
if (
|
||||
self.confirm.data
|
||||
and not (email_from and email_to)
|
||||
or email_to == email_from
|
||||
or g.user.email not in [email_from, email_to]
|
||||
):
|
||||
|
||||
errors = ["If you want confirm, you need a correct email"]
|
||||
self.user_to.errors = errors
|
||||
self.user_from.errors = errors
|
||||
|
||||
is_valid = False
|
||||
|
||||
if self.confirm.data and is_valid:
|
||||
user_to = User.query.filter_by(email=email_to).first() or g.user
|
||||
user_from = User.query.filter_by(email=email_from).first() or g.user
|
||||
if user_to == user_from:
|
||||
is_valid = False
|
||||
else:
|
||||
self.db_user_to = user_to
|
||||
self.db_user_from = user_from
|
||||
|
||||
self.has_errors = not is_valid
|
||||
return is_valid
|
||||
|
||||
def save(self, commit=True):
|
||||
if self.has_errors:
|
||||
raise ValueError(
|
||||
"The %s could not be saved because the data didn't validate."
|
||||
% (self.instance._meta.object_name)
|
||||
)
|
||||
if not self.confirm.data:
|
||||
self.create_phantom_account()
|
||||
self.prepare_instance()
|
||||
self.create_automatic_trade()
|
||||
|
||||
if commit:
|
||||
db.session.commit()
|
||||
|
||||
return self.instance
|
||||
|
||||
def prepare_instance(self):
|
||||
Model = db.Model._decl_class_registry.data['Trade']()
|
||||
self.instance = Model()
|
||||
self.instance.user_from = self.db_user_from
|
||||
self.instance.user_to = self.db_user_to
|
||||
self.instance.lot_id = self._lot.id
|
||||
self.instance.devices = self._lot.devices
|
||||
self.instance.code = self.code.data
|
||||
self.instance.confirm = self.confirm.data
|
||||
self.instance.date = self.date.data
|
||||
self.instance.name = self.name.data
|
||||
self.instance.description = self.description.data
|
||||
db.session.add(self.instance)
|
||||
|
||||
def create_phantom_account(self):
|
||||
"""
|
||||
If exist both users not to do nothing
|
||||
If exist from but not to:
|
||||
search if exist in the DB
|
||||
if exist use it
|
||||
else create new one
|
||||
The same if exist to but not from
|
||||
|
||||
"""
|
||||
user_from = self.user_from.data
|
||||
user_to = self.user_to.data
|
||||
code = self.code.data
|
||||
|
||||
if user_from and user_to:
|
||||
# both users exist, no further action is necessary
|
||||
return
|
||||
|
||||
# Create receiver (to) phantom account
|
||||
if user_from and not user_to:
|
||||
assert g.user.email == user_from
|
||||
|
||||
self.user_from = g.user
|
||||
self.user_to = self.get_or_create_user(code)
|
||||
return
|
||||
|
||||
# Create supplier (from) phantom account
|
||||
if not user_from and user_to:
|
||||
assert g.user.email == user_to
|
||||
|
||||
self.user_from = self.get_or_create_user(code)
|
||||
self.user_to = g.user
|
||||
|
||||
def get_or_create_user(self, code):
|
||||
email = "{}_{}@dhub.com".format(str(g.user.id), code)
|
||||
user = User.query.filter_by(email=email).first()
|
||||
if not user:
|
||||
user = User(email=email, password='', active=False, phantom=True)
|
||||
db.session.add(user)
|
||||
return user
|
||||
|
||||
def create_automatic_trade(self):
|
||||
# This method change the ownership of devices
|
||||
# do nothing if an explicit confirmation is required
|
||||
if self.confirm.data:
|
||||
return
|
||||
|
||||
# Change the owner for every devices
|
||||
for dev in self._lot.devices:
|
||||
dev.change_owner(self.db_user_to)
|
||||
|
||||
def check_valid(self):
|
||||
if self.user_from.data == self.user_to.data:
|
||||
return
|
||||
|
||||
if self.user_from.data == g.user.email:
|
||||
return 'user_to'
|
||||
|
||||
if self.user_to.data == g.user.email:
|
||||
return 'user_from'
|
||||
|
||||
|
||||
class TradeDocumentForm(FlaskForm):
|
||||
url = URLField(
|
||||
'Url',
|
||||
[validators.Optional()],
|
||||
render_kw={'class': "form-control"},
|
||||
description="Url where the document resides",
|
||||
)
|
||||
description = StringField(
|
||||
'Description',
|
||||
[validators.Optional()],
|
||||
render_kw={'class': "form-control"},
|
||||
description="",
|
||||
)
|
||||
id_document = StringField(
|
||||
'Document Id',
|
||||
[validators.Optional()],
|
||||
render_kw={'class': "form-control"},
|
||||
description="Identification number of document",
|
||||
)
|
||||
date = DateField(
|
||||
'Date',
|
||||
[validators.Optional()],
|
||||
render_kw={'class': "form-control"},
|
||||
description="",
|
||||
)
|
||||
file_name = FileField(
|
||||
'File',
|
||||
[validators.DataRequired()],
|
||||
render_kw={'class': "form-control"},
|
||||
description="""This file is not stored on our servers, it is only used to
|
||||
generate a digital signature and obtain the name of the file.""",
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
lot_id = kwargs.pop('lot')
|
||||
super().__init__(*args, **kwargs)
|
||||
self._lot = Lot.query.filter(Lot.id == lot_id).one()
|
||||
|
||||
def validate(self, extra_validators=None):
|
||||
is_valid = super().validate(extra_validators)
|
||||
|
||||
if g.user not in [self._lot.trade.user_from, self._lot.trade.user_to]:
|
||||
is_valid = False
|
||||
|
||||
return is_valid
|
||||
|
||||
def save(self, commit=True):
|
||||
file_name = ''
|
||||
file_hash = ''
|
||||
if self.file_name.data:
|
||||
file_name = self.file_name.data.filename
|
||||
file_hash = insert_hash(self.file_name.data.read(), commit=False)
|
||||
|
||||
self.url.data = URL(self.url.data)
|
||||
self._obj = TradeDocument(lot_id=self._lot.id)
|
||||
self.populate_obj(self._obj)
|
||||
self._obj.file_name = file_name
|
||||
self._obj.file_hash = file_hash
|
||||
db.session.add(self._obj)
|
||||
self._lot.trade.documents.add(self._obj)
|
||||
if commit:
|
||||
db.session.commit()
|
||||
|
||||
return self._obj
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import csv
|
||||
from io import StringIO
|
||||
|
||||
import flask
|
||||
from flask import Blueprint, request, url_for
|
||||
import flask_weasyprint
|
||||
from flask import Blueprint, g, make_response, request, url_for
|
||||
from flask.views import View
|
||||
from flask_login import current_user, login_required
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
from ereuse_devicehub import messages
|
||||
from ereuse_devicehub.inventory.forms import (
|
||||
|
@ -14,9 +19,14 @@ from ereuse_devicehub.inventory.forms import (
|
|||
TagDeviceForm,
|
||||
TagForm,
|
||||
TagUnnamedForm,
|
||||
TradeDocumentForm,
|
||||
TradeForm,
|
||||
UploadSnapshotForm,
|
||||
)
|
||||
from ereuse_devicehub.resources.device.models import Device
|
||||
from ereuse_devicehub.resources.action.models import Trade
|
||||
from ereuse_devicehub.resources.device.models import Computer, DataStorage, Device
|
||||
from ereuse_devicehub.resources.documents.device_row import ActionRow, DeviceRow
|
||||
from ereuse_devicehub.resources.hash_reports import insert_hash
|
||||
from ereuse_devicehub.resources.lot.models import Lot
|
||||
from ereuse_devicehub.resources.tag.model import Tag
|
||||
|
||||
|
@ -36,7 +46,7 @@ class DeviceListMix(View):
|
|||
lot = None
|
||||
tags = (
|
||||
Tag.query.filter(Tag.owner_id == current_user.id)
|
||||
.filter(Tag.device_id == None)
|
||||
.filter(Tag.device_id.is_(None))
|
||||
.order_by(Tag.created.desc())
|
||||
)
|
||||
|
||||
|
@ -47,17 +57,22 @@ class DeviceListMix(View):
|
|||
form_new_action = NewActionForm(lot=lot.id)
|
||||
form_new_allocate = AllocateForm(lot=lot.id)
|
||||
form_new_datawipe = DataWipeForm(lot=lot.id)
|
||||
form_new_trade = TradeForm(
|
||||
lot=lot.id,
|
||||
user_to=g.user.email,
|
||||
user_from=g.user.email,
|
||||
)
|
||||
else:
|
||||
devices = (
|
||||
Device.query.filter(Device.owner_id == current_user.id)
|
||||
.filter(Device.type.in_(filter_types))
|
||||
.filter(Device.lots == None)
|
||||
.filter_by(lots=None)
|
||||
.order_by(Device.updated.desc())
|
||||
)
|
||||
form_new_action = NewActionForm()
|
||||
form_new_allocate = AllocateForm()
|
||||
form_new_datawipe = DataWipeForm()
|
||||
|
||||
form_new_trade = ''
|
||||
action_devices = form_new_action.devices.data
|
||||
list_devices = []
|
||||
if action_devices:
|
||||
|
@ -71,6 +86,7 @@ class DeviceListMix(View):
|
|||
'form_new_action': form_new_action,
|
||||
'form_new_allocate': form_new_allocate,
|
||||
'form_new_datawipe': form_new_datawipe,
|
||||
'form_new_trade': form_new_trade,
|
||||
'lot': lot,
|
||||
'tags': tags,
|
||||
'list_devices': list_devices,
|
||||
|
@ -325,8 +341,10 @@ class NewActionView(View):
|
|||
self.form = self.form_class()
|
||||
|
||||
if self.form.validate_on_submit():
|
||||
instance = self.form.save()
|
||||
messages.success('Action "{}" created successfully!'.format(instance.type))
|
||||
self.form.save()
|
||||
messages.success(
|
||||
'Action "{}" created successfully!'.format(self.form.type.data)
|
||||
)
|
||||
|
||||
next_url = self.get_next_url()
|
||||
return flask.redirect(next_url)
|
||||
|
@ -348,8 +366,10 @@ class NewAllocateView(NewActionView, DeviceListMix):
|
|||
self.form = self.form_class()
|
||||
|
||||
if self.form.validate_on_submit():
|
||||
instance = self.form.save()
|
||||
messages.success('Action "{}" created successfully!'.format(instance.type))
|
||||
self.form.save()
|
||||
messages.success(
|
||||
'Action "{}" created successfully!'.format(self.form.type.data)
|
||||
)
|
||||
|
||||
next_url = self.get_next_url()
|
||||
return flask.redirect(next_url)
|
||||
|
@ -368,8 +388,10 @@ class NewDataWipeView(NewActionView, DeviceListMix):
|
|||
self.form = self.form_class()
|
||||
|
||||
if self.form.validate_on_submit():
|
||||
instance = self.form.save()
|
||||
messages.success('Action "{}" created successfully!'.format(instance.type))
|
||||
self.form.save()
|
||||
messages.success(
|
||||
'Action "{}" created successfully!'.format(self.form.type.data)
|
||||
)
|
||||
|
||||
next_url = self.get_next_url()
|
||||
return flask.redirect(next_url)
|
||||
|
@ -380,13 +402,186 @@ class NewDataWipeView(NewActionView, DeviceListMix):
|
|||
return flask.render_template(self.template_name, **self.context)
|
||||
|
||||
|
||||
class NewTradeView(NewActionView, DeviceListMix):
|
||||
methods = ['POST']
|
||||
form_class = TradeForm
|
||||
|
||||
def dispatch_request(self):
|
||||
self.form = self.form_class()
|
||||
|
||||
if self.form.validate_on_submit():
|
||||
self.form.save()
|
||||
messages.success(
|
||||
'Action "{}" created successfully!'.format(self.form.type.data)
|
||||
)
|
||||
|
||||
next_url = self.get_next_url()
|
||||
return flask.redirect(next_url)
|
||||
|
||||
lot_id = self.form.lot.data
|
||||
self.get_context(lot_id)
|
||||
self.context['form_new_trade'] = self.form
|
||||
return flask.render_template(self.template_name, **self.context)
|
||||
|
||||
|
||||
class NewTradeDocumentView(View):
|
||||
methods = ['POST', 'GET']
|
||||
decorators = [login_required]
|
||||
template_name = 'inventory/trade_document.html'
|
||||
form_class = TradeDocumentForm
|
||||
title = "Add new document"
|
||||
|
||||
def dispatch_request(self, lot_id):
|
||||
self.form = self.form_class(lot=lot_id)
|
||||
|
||||
if self.form.validate_on_submit():
|
||||
self.form.save()
|
||||
messages.success('Document created successfully!')
|
||||
next_url = url_for('inventory.devices.lotdevicelist', lot_id=lot_id)
|
||||
return flask.redirect(next_url)
|
||||
|
||||
return flask.render_template(
|
||||
self.template_name, form=self.form, title=self.title
|
||||
)
|
||||
|
||||
|
||||
class ExportsView(View):
|
||||
methods = ['GET']
|
||||
decorators = [login_required]
|
||||
|
||||
def dispatch_request(self, export_id):
|
||||
export_ids = {
|
||||
'metrics': self.metrics,
|
||||
'devices': self.devices_list,
|
||||
'certificates': self.erasure,
|
||||
'links': self.public_links,
|
||||
}
|
||||
|
||||
if export_id not in export_ids:
|
||||
return NotFound()
|
||||
return export_ids[export_id]()
|
||||
|
||||
def find_devices(self):
|
||||
args = request.args.get('ids')
|
||||
ids = args.split(',') if args else []
|
||||
query = Device.query.filter(Device.owner == g.user)
|
||||
return query.filter(Device.devicehub_id.in_(ids))
|
||||
|
||||
def response_csv(self, data, name):
|
||||
bfile = data.getvalue().encode('utf-8')
|
||||
# insert proof
|
||||
insert_hash(bfile)
|
||||
output = make_response(bfile)
|
||||
output.headers['Content-Disposition'] = 'attachment; filename={}'.format(name)
|
||||
output.headers['Content-type'] = 'text/csv'
|
||||
return output
|
||||
|
||||
def devices_list(self):
|
||||
"""Get device query and put information in csv format."""
|
||||
data = StringIO()
|
||||
cw = csv.writer(data, delimiter=';', lineterminator="\n", quotechar='"')
|
||||
first = True
|
||||
|
||||
for device in self.find_devices():
|
||||
d = DeviceRow(device, {})
|
||||
if first:
|
||||
cw.writerow(d.keys())
|
||||
first = False
|
||||
cw.writerow(d.values())
|
||||
|
||||
return self.response_csv(data, "export.csv")
|
||||
|
||||
def metrics(self):
|
||||
"""Get device query and put information in csv format."""
|
||||
data = StringIO()
|
||||
cw = csv.writer(data, delimiter=';', lineterminator="\n", quotechar='"')
|
||||
first = True
|
||||
devs_id = []
|
||||
# Get the allocate info
|
||||
for device in self.find_devices():
|
||||
devs_id.append(device.id)
|
||||
for allocate in device.get_metrics():
|
||||
d = ActionRow(allocate)
|
||||
if first:
|
||||
cw.writerow(d.keys())
|
||||
first = False
|
||||
cw.writerow(d.values())
|
||||
|
||||
# Get the trade info
|
||||
query_trade = Trade.query.filter(
|
||||
Trade.devices.any(Device.id.in_(devs_id))
|
||||
).all()
|
||||
|
||||
lot_id = request.args.get('lot')
|
||||
if lot_id and not query_trade:
|
||||
lot = Lot.query.filter_by(id=lot_id).one()
|
||||
if hasattr(lot, "trade") and lot.trade:
|
||||
if g.user in [lot.trade.user_from, lot.trade.user_to]:
|
||||
query_trade = [lot.trade]
|
||||
|
||||
for trade in query_trade:
|
||||
data_rows = trade.get_metrics()
|
||||
for row in data_rows:
|
||||
d = ActionRow(row)
|
||||
if first:
|
||||
cw.writerow(d.keys())
|
||||
first = False
|
||||
cw.writerow(d.values())
|
||||
|
||||
return self.response_csv(data, "actions_export.csv")
|
||||
|
||||
def public_links(self):
|
||||
# get a csv with the publink links of this devices
|
||||
data = StringIO()
|
||||
cw = csv.writer(data, delimiter=';', lineterminator="\n", quotechar='"')
|
||||
cw.writerow(['links'])
|
||||
host_url = request.host_url
|
||||
for dev in self.find_devices():
|
||||
code = dev.devicehub_id
|
||||
link = [f"{host_url}devices/{code}"]
|
||||
cw.writerow(link)
|
||||
|
||||
return self.response_csv(data, "links.csv")
|
||||
|
||||
def erasure(self):
|
||||
template = self.build_erasure_certificate()
|
||||
res = flask_weasyprint.render_pdf(
|
||||
flask_weasyprint.HTML(string=template),
|
||||
download_filename='erasure-certificate.pdf',
|
||||
)
|
||||
insert_hash(res.data)
|
||||
return res
|
||||
|
||||
def build_erasure_certificate(self):
|
||||
erasures = []
|
||||
for device in self.find_devices():
|
||||
if isinstance(device, Computer):
|
||||
for privacy in device.privacy:
|
||||
erasures.append(privacy)
|
||||
elif isinstance(device, DataStorage):
|
||||
if device.privacy:
|
||||
erasures.append(device.privacy)
|
||||
|
||||
params = {
|
||||
'title': 'Erasure Certificate',
|
||||
'erasures': tuple(erasures),
|
||||
'url_pdf': '',
|
||||
}
|
||||
return flask.render_template('inventory/erasure.html', **params)
|
||||
|
||||
|
||||
devices.add_url_rule('/action/add/', view_func=NewActionView.as_view('action_add'))
|
||||
devices.add_url_rule('/action/trade/add/', view_func=NewTradeView.as_view('trade_add'))
|
||||
devices.add_url_rule(
|
||||
'/action/allocate/add/', view_func=NewAllocateView.as_view('allocate_add')
|
||||
)
|
||||
devices.add_url_rule(
|
||||
'/action/datawipe/add/', view_func=NewDataWipeView.as_view('datawipe_add')
|
||||
)
|
||||
devices.add_url_rule(
|
||||
'/lot/<string:lot_id>/trade-document/add/',
|
||||
view_func=NewTradeDocumentView.as_view('trade_document_add'),
|
||||
)
|
||||
devices.add_url_rule('/device/', view_func=DeviceListView.as_view('devicelist'))
|
||||
devices.add_url_rule(
|
||||
'/device/<string:id>/', view_func=DeviceDetailView.as_view('device_details')
|
||||
|
@ -424,3 +619,6 @@ devices.add_url_rule(
|
|||
'/tag/devices/<int:id>/del/',
|
||||
view_func=TagUnlinkDeviceView.as_view('tag_devices_del'),
|
||||
)
|
||||
devices.add_url_rule(
|
||||
'/export/<string:export_id>/', view_func=ExportsView.as_view('export')
|
||||
)
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
$(document).ready(function() {
|
||||
var show_allocate_form = $("#allocateModal").data('show-action-form');
|
||||
var show_datawipe_form = $("#datawipeModal").data('show-action-form');
|
||||
var show_trade_form = $("#tradeLotModal").data('show-action-form');
|
||||
if (show_allocate_form != "None") {
|
||||
$("#allocateModal .btn-primary").show();
|
||||
newAllocate(show_allocate_form);
|
||||
} else if (show_datawipe_form != "None") {
|
||||
$("#datawipeModal .btn-primary").show();
|
||||
newDataWipe(show_datawipe_form);
|
||||
} else if (show_trade_form != "None") {
|
||||
$("#tradeLotModal .btn-primary").show();
|
||||
newTrade(show_trade_form);
|
||||
} else {
|
||||
$(".deviceSelect").on("change", deviceSelect);
|
||||
}
|
||||
|
@ -15,6 +19,7 @@ $(document).ready(function() {
|
|||
|
||||
function deviceSelect() {
|
||||
var devices_count = $(".deviceSelect").filter(':checked').length;
|
||||
get_device_list();
|
||||
if (devices_count == 0) {
|
||||
$("#addingLotModal .pol").show();
|
||||
$("#addingLotModal .btn-primary").hide();
|
||||
|
@ -50,23 +55,48 @@ function deviceSelect() {
|
|||
$("#datawipeModal .btn-primary").show();
|
||||
|
||||
$("#addingTagModal .pol").hide();
|
||||
$("#addingTagModal .btn-primary").show();
|
||||
}
|
||||
}
|
||||
|
||||
function removeTag() {
|
||||
var devices = $(".deviceSelect").filter(':checked');
|
||||
var devices_id = $.map(devices, function(x) { return $(x).attr('data')});
|
||||
console.log(devices_id);
|
||||
if (devices_id.length > 0) {
|
||||
var url = "/inventory/tag/devices/"+devices_id[0]+"/del/";
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
|
||||
function addTag() {
|
||||
deviceSelect();
|
||||
$("#addingTagModal").click();
|
||||
}
|
||||
|
||||
function newTrade(action) {
|
||||
var title = "Trade "
|
||||
var user_to = $("#user_to").data("email");
|
||||
var user_from = $("#user_from").data("email");
|
||||
if (action == 'user_from') {
|
||||
title = 'Trade Incoming';
|
||||
$("#user_to").attr('readonly', 'readonly');
|
||||
$("#user_from").prop('readonly', false);
|
||||
$("#user_from").val('');
|
||||
$("#user_to").val(user_to);
|
||||
} else if (action == 'user_to') {
|
||||
title = 'Trade Outgoing';
|
||||
$("#user_from").attr('readonly', 'readonly');
|
||||
$("#user_to").prop('readonly', false);
|
||||
$("#user_to").val('');
|
||||
$("#user_from").val(user_from);
|
||||
}
|
||||
$("#tradeLotModal #title-action").html(title);
|
||||
$("#activeTradeModal").click();
|
||||
}
|
||||
|
||||
function newAction(action) {
|
||||
$("#actionModal #type").val(action);
|
||||
$("#actionModal #title-action").html(action);
|
||||
get_device_list();
|
||||
deviceSelect();
|
||||
$("#activeActionModal").click();
|
||||
}
|
||||
|
@ -74,7 +104,6 @@ function newAction(action) {
|
|||
function newAllocate(action) {
|
||||
$("#allocateModal #type").val(action);
|
||||
$("#allocateModal #title-action").html(action);
|
||||
get_device_list();
|
||||
deviceSelect();
|
||||
$("#activeAllocateModal").click();
|
||||
}
|
||||
|
@ -82,7 +111,6 @@ function newAllocate(action) {
|
|||
function newDataWipe(action) {
|
||||
$("#datawipeModal #type").val(action);
|
||||
$("#datawipeModal #title-action").html(action);
|
||||
get_device_list();
|
||||
deviceSelect();
|
||||
$("#activeDatawipeModal").click();
|
||||
}
|
||||
|
@ -120,3 +148,14 @@ function get_device_list() {
|
|||
description = $.map(list_devices, function(x) { return x }).join(", ");
|
||||
$(".enumeration-devices").html(description);
|
||||
}
|
||||
|
||||
function export_file(type_file) {
|
||||
var devices = $(".deviceSelect").filter(':checked');
|
||||
var devices_id = $.map(devices, function(x) { return $(x).attr('data-device-dhid')}).join(",");
|
||||
if (devices_id){
|
||||
var url = "/inventory/export/"+type_file+"/?ids="+devices_id;
|
||||
window.location.href = url;
|
||||
} else {
|
||||
$("#exportAlertModal").click();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<input type="submit" class="btn btn-primary d-none" value="Save changes" />
|
||||
<input type="submit" class="btn btn-primary" style="display: none;" value="Save changes" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
<div class="modal fade" id="exportErrorModal" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Error export</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<p class="text-danger pol">
|
||||
You need select first some device for use export file
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,5 +1,5 @@
|
|||
<div class="modal fade" id="allocateModal" tabindex="-1" style="display: none;" aria-hidden="true"
|
||||
data-show-action-form="{{ form_new_allocate.type.data }}">
|
||||
data-show-action-form="{{ form_new_allocate.check_valid() }}">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="modal fade" id="datawipeModal" tabindex="-1" style="display: none;" aria-hidden="true"
|
||||
data-show-action-form="{{ form_new_datawipe.type.data }}">
|
||||
data-show-action-form="{{ form_new_datawipe.check_valid() }}">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
|
||||
|
|
|
@ -68,22 +68,40 @@
|
|||
<div class="card">
|
||||
<div class="card-body pt-3" style="min-height: 650px;">
|
||||
<!-- Bordered Tabs -->
|
||||
{% if lot and not lot.is_temporary %}
|
||||
<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>
|
||||
|
||||
<li class="nav-item">
|
||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#trade-documents-list">Documents</button>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
{% endif %}
|
||||
<div class="tab-content pt-5">
|
||||
<div id="devices-list" class="tab-pane fade devices-list active show">
|
||||
|
||||
<div class="btn-group dropdown ml-1">
|
||||
{% if lot and lot.is_temporary and not lot.devices %}
|
||||
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="modal" data-bs-target="#btnRemoveLots">
|
||||
<i class="bi bi-trash"></i>
|
||||
Remove Lot
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
{% else %}
|
||||
<button id="btnLots" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-folder2"></i>
|
||||
Lots
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
{% endif %}
|
||||
<span class="d-none" id="activeTradeModal" data-bs-toggle="modal" data-bs-target="#tradeLotModal"></span>
|
||||
<ul class="dropdown-menu" aria-labelledby="btnLots">
|
||||
{% if lot and lot.is_temporary and not lot.devices %}
|
||||
<li>
|
||||
<a href="javascript:newAction('Use')" class="dropdown-item"
|
||||
data-bs-toggle="modal" data-bs-target="#btnRemoveLots">
|
||||
<i class="bi bi-trash"></i>
|
||||
Remove Lot
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a href="javascript:void()" class="dropdown-item" data-bs-toggle="modal" data-bs-target="#addingLotModal">
|
||||
<i class="bi bi-plus"></i>
|
||||
|
@ -96,9 +114,31 @@
|
|||
Remove selected devices from a lot
|
||||
</a>
|
||||
</li>
|
||||
{% if lot.is_temporary %}
|
||||
<li>
|
||||
<a href="javascript:newTrade('user_from')" class="dropdown-item">
|
||||
<i class="bi bi-plus"></i>
|
||||
Add supplier
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:newTrade('user_to')" class="dropdown-item">
|
||||
<i class="bi bi-plus"></i>
|
||||
Add receiver
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if lot and not lot.is_temporary %}
|
||||
<li>
|
||||
<a href="{{ url_for('inventory.devices.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>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="btn-group dropdown ml-1" uib-dropdown="">
|
||||
<button id="btnActions" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-plus"></i>
|
||||
|
@ -191,10 +231,30 @@
|
|||
<i class="bi bi-reply"></i>
|
||||
Exports
|
||||
</button>
|
||||
<span class="d-none" id="exportAlertModal" data-bs-toggle="modal" data-bs-target="#exportErrorModal"></span>
|
||||
<ul class="dropdown-menu" aria-labelledby="btnExport">
|
||||
<li>
|
||||
<a href="#" class="dropdown-item">
|
||||
TODO: Not implemented
|
||||
<a href="javascript:export_file('devices')" class="dropdown-item">
|
||||
<i class="bi bi-file-spreadsheet"></i>
|
||||
Devices Spreadsheet
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:export_file('metrics')" class="dropdown-item">
|
||||
<i class="bi bi-file-spreadsheet"></i>
|
||||
Metrics Spreadsheet
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:export_file('links')" class="dropdown-item">
|
||||
<i class="bi bi-link-45deg"></i>
|
||||
Public Links
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:export_file('certificates')" class="dropdown-item">
|
||||
<i class="bi bi-eraser-fill"></i>
|
||||
Erasure Certificate
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -207,7 +267,7 @@
|
|||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="btnTags">
|
||||
<li>
|
||||
<a href="javascript:void()" class="dropdown-item" data-bs-toggle="modal" data-bs-target="#addingTagModal">
|
||||
<a href="javascript:addTag()" class="dropdown-item" data-bs-toggle="modal" data-bs-target="#addingTagModal">
|
||||
<i class="bi bi-plus"></i>
|
||||
Add Tag to selected Devices
|
||||
</a>
|
||||
|
@ -244,8 +304,6 @@
|
|||
|
||||
<div class="tab-content pt-2">
|
||||
|
||||
<div class="tab-pane fade show active profile-overview" id="profile-overview">
|
||||
|
||||
<h5 class="card-title">Computers</h5>
|
||||
<table class="table">
|
||||
<thead>
|
||||
|
@ -293,8 +351,40 @@
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
</div><!-- End Bordered Tabs -->
|
||||
</div>
|
||||
</div>
|
||||
{% if lot and not lot.is_temporary %}
|
||||
<div id="trade-documents-list" class="tab-pane fade trade-documents-list">
|
||||
<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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for doc in lot.trade.documents %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if doc.url.to_text() %}
|
||||
<a href="{{ doc.url.to_text() }}" target="_blank">{{ doc.file_name}}</a>
|
||||
{% else %}
|
||||
{{ doc.file_name}}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ doc.created.strftime('%H:%M %d-%m-%Y')}}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div><!-- End Bordered Tabs -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -308,6 +398,8 @@
|
|||
{% include "inventory/actions.html" %}
|
||||
{% include "inventory/allocate.html" %}
|
||||
{% include "inventory/data_wipe.html" %}
|
||||
{% include "inventory/trade.html" %}
|
||||
{% include "inventory/alert_export_error.html" %}
|
||||
|
||||
<!-- CDN -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/simple-datatables@latest"></script>
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
{% extends "documents/layout.html" %}
|
||||
{% block body %}
|
||||
<div>
|
||||
<h2>Summary</h2>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>S/N Data Storage</th>
|
||||
<th>Type of erasure</th>
|
||||
<th>Result</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for erasure in erasures %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ erasure.device.serial_number.upper() }}
|
||||
</td>
|
||||
<td>
|
||||
{{ erasure.type }}
|
||||
</td>
|
||||
<td>
|
||||
{{ erasure.severity }}
|
||||
</td>
|
||||
<td>
|
||||
{{ erasure.date_str }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="page-break row">
|
||||
<h2>Details</h2>
|
||||
{% for erasure in erasures %}
|
||||
<div class="col-md-6 no-page-break">
|
||||
<h4>{{ erasure.device.__format__('t') }}</h4>
|
||||
<dl>
|
||||
<dt>Data storage:</dt>
|
||||
<dd>{{ erasure.device.__format__('ts') }}</dd>
|
||||
|
||||
<dt>Computer where was erase:</dt>
|
||||
<dd>Title: {{ erasure.parent.__format__('ts') }}</dd>
|
||||
<dd>DevicehubID: {{ erasure.parent.devicehub_id }}</dd>
|
||||
<dd>Hid: {{ erasure.parent.hid }}</dd>
|
||||
<dd>Tags: {{ erasure.parent.tags }}</dd>
|
||||
|
||||
<dt>Computer where it resides:</dt>
|
||||
<dd>Title: {{ erasure.device.parent.__format__('ts') }}</dd>
|
||||
<dd>DevicehubID: {{ erasure.device.parent.devicehub_id }}</dd>
|
||||
<dd>Hid: {{ erasure.device.parent.hid }}</dd>
|
||||
<dd>Tags: {{ erasure.device.parent.tags }}</dd>
|
||||
|
||||
<dt>Erasure:</dt>
|
||||
<dd>{{ erasure.__format__('ts') }}</dd>
|
||||
{% if erasure.steps %}
|
||||
<dt>Erasure steps:</dt>
|
||||
<dd>
|
||||
<ol>
|
||||
{% for step in erasure.steps %}
|
||||
<li>{{ step.__format__('') }}</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="no-page-break">
|
||||
<h2>Glossary</h2>
|
||||
<dl>
|
||||
<dt>Erase Basic</dt>
|
||||
<dd>
|
||||
A software-based fast non-100%-secured way of erasing data storage,
|
||||
using <a href="https://en.wikipedia.org/wiki/Shred_(Unix)">shred</a>.
|
||||
</dd>
|
||||
<dt>Erase Sectors</dt>
|
||||
<dd>
|
||||
A secured-way of erasing data storages, checking sector-by-sector
|
||||
the erasure, using <a href="https://en.wikipedia.org/wiki/Badblocks">badblocks</a>.
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="no-print">
|
||||
<a href="{{ url_pdf }}">Click here to download the PDF.</a>
|
||||
</div>
|
||||
<div class="print-only">
|
||||
<a href="{{ url_for('Document.StampsView', _external=True) }}">Verify on-line the integrity of this document</a>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -0,0 +1,62 @@
|
|||
{% if form_new_trade %}
|
||||
<div class="modal fade" id="tradeLotModal" tabindex="-1" style="display: none;" aria-hidden="true"
|
||||
data-show-action-form="{{ form_new_trade.check_valid() }}">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">New Action <span id="title-action"></span></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<form action="{{ url_for('inventory.devices.trade_add') }}" method="post">
|
||||
{{ form_new_trade.csrf_token }}
|
||||
<div class="modal-body">
|
||||
{% for field in form_new_trade %}
|
||||
{% if field != form_new_trade.csrf_token %}
|
||||
{% if field == form_new_trade.devices %}
|
||||
{{ field }}
|
||||
{% elif field == form_new_trade.lot %}
|
||||
{{ field }}
|
||||
{% elif field == form_new_trade.type %}
|
||||
{{ field }}
|
||||
{% else %}
|
||||
<div class="col-12">
|
||||
{{ field.label(class_="form-label") }}
|
||||
{% if field == form_new_trade.confirm %}
|
||||
<div class="form-check form-switch">
|
||||
{{ field(class_="form-check-input") }}
|
||||
<small class="text-muted">{{ field.description }}</small>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ field(class_="form-control") }}
|
||||
<small class="text-muted">{{ field.description }}</small>
|
||||
{% endif %}
|
||||
{% if field.errors %}
|
||||
<p class="text-danger">
|
||||
{% for error in field.errors %}
|
||||
{{ error }}<br/>
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<input type="submit" class="btn btn-primary" value="Create" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="modal fade" id="tradeLotModal" tabindex="-1" style="display: none;" aria-hidden="true"
|
||||
data-show-action-form="None">
|
||||
</div>
|
||||
{% endif %}
|
|
@ -0,0 +1,65 @@
|
|||
{% extends "ereuse_devicehub/base_site.html" %}
|
||||
{% block main %}
|
||||
|
||||
<div class="pagetitle">
|
||||
<h1>{{ title }}</h1>
|
||||
<nav>
|
||||
<ol class="breadcrumb">
|
||||
<!-- TODO@slamora replace with lot list URL when exists -->
|
||||
<li class="breadcrumb-item"><a href="#TODO-lot-list">Lots</a></li>
|
||||
<li class="breadcrumb-item">Trade Document</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div><!-- End Page Title -->
|
||||
|
||||
<section class="section profile">
|
||||
<div class="row">
|
||||
<div class="col-xl-4">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
<div class="pt-4 pb-2">
|
||||
<h5 class="card-title text-center pb-0 fs-4">{{ title }}</h5>
|
||||
{% if form.form_errors %}
|
||||
<p class="text-danger">
|
||||
{% for error in form.form_errors %}
|
||||
{{ error }}<br/>
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<form action="{{ url_for('inventory.devices.trade_document_add', lot_id=form._lot.id) }}" method="post"
|
||||
class="row g-3 needs-validation" enctype="multipart/form-data">
|
||||
{{ form.csrf_token }}
|
||||
{% for field in form %}
|
||||
{% if field != form.csrf_token %}
|
||||
<div class="col-12">
|
||||
{{ field.label(class_="form-label") }}
|
||||
{{ field }}
|
||||
<small class="text-muted">{{ field.description }}</small>
|
||||
{% endif %}
|
||||
{% if field.errors %}
|
||||
<p class="text-danger">
|
||||
{% for error in field.errors %}
|
||||
{{ error }}<br/>
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="col-12">
|
||||
<a href="{{ url_for('inventory.devices.lotdevicelist', lot_id=form._lot.id) }}" class="btn btn-danger">Cancel</a>
|
||||
<button class="btn btn-primary" type="submit">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock main %}
|
Reference in New Issue