Merge branch 'testing' into without-teal
This commit is contained in:
commit
ae5992f4c0
|
@ -32,6 +32,7 @@ from wtforms.fields import FormField
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.inventory.models import (
|
from ereuse_devicehub.inventory.models import (
|
||||||
DeliveryNote,
|
DeliveryNote,
|
||||||
|
DeviceDocument,
|
||||||
ReceiverNote,
|
ReceiverNote,
|
||||||
Transfer,
|
Transfer,
|
||||||
TransferCustomerDetails,
|
TransferCustomerDetails,
|
||||||
|
@ -110,6 +111,15 @@ DEVICES = {
|
||||||
"Other Devices": ["Other"],
|
"Other Devices": ["Other"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TYPES_DOCUMENTS = [
|
||||||
|
("", ""),
|
||||||
|
("image", "Image"),
|
||||||
|
("main_image", "Main Image"),
|
||||||
|
("functionality_report", "Functionality Report"),
|
||||||
|
("data_sanitization_report", "Data Sanitization Report"),
|
||||||
|
("disposition_report", "Disposition Report"),
|
||||||
|
]
|
||||||
|
|
||||||
COMPUTERS = ['Desktop', 'Laptop', 'Server', 'Computer']
|
COMPUTERS = ['Desktop', 'Laptop', 'Server', 'Computer']
|
||||||
|
|
||||||
MONITORS = ["ComputerMonitor", "Monitor", "TelevisionSet", "Projector"]
|
MONITORS = ["ComputerMonitor", "Monitor", "TelevisionSet", "Projector"]
|
||||||
|
@ -1274,8 +1284,20 @@ class TradeDocumentForm(FlaskForm):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
lot_id = kwargs.pop('lot')
|
lot_id = kwargs.pop('lot')
|
||||||
super().__init__(*args, **kwargs)
|
doc_id = kwargs.pop('document', None)
|
||||||
self._lot = Lot.query.filter(Lot.id == lot_id).one()
|
self._lot = Lot.query.filter(Lot.id == lot_id).one()
|
||||||
|
self._obj = None
|
||||||
|
if doc_id:
|
||||||
|
self._obj = TradeDocument.query.filter_by(
|
||||||
|
id=doc_id, lot=self._lot, owner=g.user
|
||||||
|
).one()
|
||||||
|
kwargs['obj'] = self._obj
|
||||||
|
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if self._obj:
|
||||||
|
if isinstance(self.url.data, URL):
|
||||||
|
self.url.data = self.url.data.to_text()
|
||||||
|
|
||||||
if not self._lot.transfer:
|
if not self._lot.transfer:
|
||||||
self.form_errors = ['Error, this lot is not a transfer lot.']
|
self.form_errors = ['Error, this lot is not a transfer lot.']
|
||||||
|
@ -1296,17 +1318,126 @@ class TradeDocumentForm(FlaskForm):
|
||||||
file_hash = insert_hash(self.file_name.data.read(), commit=False)
|
file_hash = insert_hash(self.file_name.data.read(), commit=False)
|
||||||
|
|
||||||
self.url.data = URL(self.url.data)
|
self.url.data = URL(self.url.data)
|
||||||
self._obj = TradeDocument(lot_id=self._lot.id)
|
if not self._obj:
|
||||||
|
self._obj = TradeDocument(lot_id=self._lot.id)
|
||||||
|
|
||||||
self.populate_obj(self._obj)
|
self.populate_obj(self._obj)
|
||||||
|
|
||||||
self._obj.file_name = file_name
|
self._obj.file_name = file_name
|
||||||
self._obj.file_hash = file_hash
|
self._obj.file_hash = file_hash
|
||||||
db.session.add(self._obj)
|
|
||||||
self._lot.documents.add(self._obj)
|
if not self._obj.id:
|
||||||
|
db.session.add(self._obj)
|
||||||
|
self._lot.documents.add(self._obj)
|
||||||
|
|
||||||
if commit:
|
if commit:
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return self._obj
|
return self._obj
|
||||||
|
|
||||||
|
def remove(self):
|
||||||
|
if self._obj:
|
||||||
|
self._obj.delete()
|
||||||
|
db.session.commit()
|
||||||
|
return self._obj
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceDocumentForm(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",
|
||||||
|
)
|
||||||
|
type = SelectField(
|
||||||
|
'Type',
|
||||||
|
[validators.Optional()],
|
||||||
|
choices=TYPES_DOCUMENTS,
|
||||||
|
default="",
|
||||||
|
render_kw={'class': "form-select"},
|
||||||
|
)
|
||||||
|
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):
|
||||||
|
id = kwargs.pop('dhid')
|
||||||
|
doc_id = kwargs.pop('document', None)
|
||||||
|
self._device = Device.query.filter(Device.devicehub_id == id).first()
|
||||||
|
self._obj = None
|
||||||
|
if doc_id:
|
||||||
|
self._obj = DeviceDocument.query.filter_by(
|
||||||
|
id=doc_id, device=self._device, owner=g.user
|
||||||
|
).one()
|
||||||
|
kwargs['obj'] = self._obj
|
||||||
|
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if self._obj:
|
||||||
|
if isinstance(self.url.data, URL):
|
||||||
|
self.url.data = self.url.data.to_text()
|
||||||
|
|
||||||
|
def validate(self, extra_validators=None):
|
||||||
|
is_valid = super().validate(extra_validators)
|
||||||
|
|
||||||
|
if g.user != self._device.owner:
|
||||||
|
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)
|
||||||
|
if not self._obj:
|
||||||
|
self._obj = DeviceDocument(device_id=self._device.id)
|
||||||
|
|
||||||
|
self.populate_obj(self._obj)
|
||||||
|
|
||||||
|
self._obj.file_name = file_name
|
||||||
|
self._obj.file_hash = file_hash
|
||||||
|
|
||||||
|
if not self._obj.id:
|
||||||
|
db.session.add(self._obj)
|
||||||
|
# self._device.documents.add(self._obj)
|
||||||
|
|
||||||
|
if commit:
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return self._obj
|
||||||
|
|
||||||
|
def remove(self):
|
||||||
|
if self._obj:
|
||||||
|
self._obj.delete()
|
||||||
|
db.session.commit()
|
||||||
|
return self._obj
|
||||||
|
|
||||||
|
|
||||||
class TransferForm(FlaskForm):
|
class TransferForm(FlaskForm):
|
||||||
lot_name = StringField(
|
lot_name = StringField(
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from citext import CIText
|
from citext import CIText
|
||||||
|
from dateutil.tz import tzutc
|
||||||
from flask import g
|
from flask import g
|
||||||
from sqlalchemy import Column, Integer
|
from sortedcontainers import SortedSet
|
||||||
|
from sqlalchemy import BigInteger, Column, Integer
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
from sqlalchemy.orm import backref, relationship
|
from sqlalchemy.orm import backref, relationship
|
||||||
|
|
||||||
|
@ -110,3 +112,50 @@ class TransferCustomerDetails(Thing):
|
||||||
),
|
),
|
||||||
primaryjoin='TransferCustomerDetails.transfer_id == Transfer.id',
|
primaryjoin='TransferCustomerDetails.transfer_id == Transfer.id',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_sorted_documents = {
|
||||||
|
'order_by': lambda: DeviceDocument.created,
|
||||||
|
'collection_class': SortedSet,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceDocument(Thing):
|
||||||
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
||||||
|
type = Column(db.CIText(), nullable=True)
|
||||||
|
date = Column(db.DateTime, nullable=True)
|
||||||
|
id_document = Column(db.CIText(), nullable=True)
|
||||||
|
description = Column(db.CIText(), nullable=True)
|
||||||
|
owner_id = db.Column(
|
||||||
|
UUID(as_uuid=True),
|
||||||
|
db.ForeignKey(User.id),
|
||||||
|
nullable=False,
|
||||||
|
default=lambda: g.user.id,
|
||||||
|
)
|
||||||
|
owner = db.relationship(User, primaryjoin=owner_id == User.id)
|
||||||
|
device_id = db.Column(BigInteger, db.ForeignKey('device.id'), nullable=False)
|
||||||
|
device = db.relationship(
|
||||||
|
'Device',
|
||||||
|
primaryjoin='DeviceDocument.device_id == Device.id',
|
||||||
|
backref=backref(
|
||||||
|
'documents', lazy=True, cascade=CASCADE_OWN, **_sorted_documents
|
||||||
|
),
|
||||||
|
)
|
||||||
|
file_name = Column(db.CIText(), nullable=True)
|
||||||
|
file_hash = Column(db.CIText(), nullable=True)
|
||||||
|
url = db.Column(URL(), nullable=True)
|
||||||
|
|
||||||
|
# __table_args__ = (
|
||||||
|
# db.Index('document_id', id, postgresql_using='hash'),
|
||||||
|
# db.Index('type_doc', type, postgresql_using='hash')
|
||||||
|
# )
|
||||||
|
|
||||||
|
def get_url(self) -> str:
|
||||||
|
if self.url:
|
||||||
|
return self.url.to_text()
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.created.replace(tzinfo=tzutc()) < other.created.replace(
|
||||||
|
tzinfo=tzutc()
|
||||||
|
)
|
||||||
|
|
|
@ -24,6 +24,7 @@ from ereuse_devicehub.inventory.forms import (
|
||||||
BindingForm,
|
BindingForm,
|
||||||
CustomerDetailsForm,
|
CustomerDetailsForm,
|
||||||
DataWipeForm,
|
DataWipeForm,
|
||||||
|
DeviceDocumentForm,
|
||||||
EditTransferForm,
|
EditTransferForm,
|
||||||
FilterForm,
|
FilterForm,
|
||||||
LotForm,
|
LotForm,
|
||||||
|
@ -547,6 +548,27 @@ class LotDeleteView(View):
|
||||||
return flask.redirect(next_url)
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentDeleteView(View):
|
||||||
|
methods = ['GET']
|
||||||
|
decorators = [login_required]
|
||||||
|
template_name = 'inventory/device_list.html'
|
||||||
|
form_class = TradeDocumentForm
|
||||||
|
|
||||||
|
def dispatch_request(self, lot_id, doc_id):
|
||||||
|
next_url = url_for('inventory.lotdevicelist', lot_id=lot_id)
|
||||||
|
form = self.form_class(lot=lot_id, document=doc_id)
|
||||||
|
try:
|
||||||
|
form.remove()
|
||||||
|
except Exception as err:
|
||||||
|
msg = "{}".format(err)
|
||||||
|
messages.error(msg)
|
||||||
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
|
msg = "Document removed successfully."
|
||||||
|
messages.success(msg)
|
||||||
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
|
|
||||||
class UploadSnapshotView(GenericMixin):
|
class UploadSnapshotView(GenericMixin):
|
||||||
methods = ['GET', 'POST']
|
methods = ['GET', 'POST']
|
||||||
decorators = [login_required]
|
decorators = [login_required]
|
||||||
|
@ -789,6 +811,69 @@ class NewTradeView(DeviceListMixin, NewActionView):
|
||||||
return flask.redirect(next_url)
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
|
|
||||||
|
class NewDeviceDocumentView(GenericMixin):
|
||||||
|
methods = ['POST', 'GET']
|
||||||
|
decorators = [login_required]
|
||||||
|
template_name = 'inventory/device_document.html'
|
||||||
|
form_class = DeviceDocumentForm
|
||||||
|
title = "Add new document"
|
||||||
|
|
||||||
|
def dispatch_request(self, dhid):
|
||||||
|
self.form = self.form_class(dhid=dhid)
|
||||||
|
self.get_context()
|
||||||
|
|
||||||
|
if self.form.validate_on_submit():
|
||||||
|
self.form.save()
|
||||||
|
messages.success('Document created successfully!')
|
||||||
|
next_url = url_for('inventory.device_details', id=dhid)
|
||||||
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
|
self.context.update({'form': self.form, 'title': self.title})
|
||||||
|
return flask.render_template(self.template_name, **self.context)
|
||||||
|
|
||||||
|
|
||||||
|
class EditDeviceDocumentView(GenericMixin):
|
||||||
|
decorators = [login_required]
|
||||||
|
methods = ['POST', 'GET']
|
||||||
|
template_name = 'inventory/device_document.html'
|
||||||
|
form_class = DeviceDocumentForm
|
||||||
|
title = "Edit document"
|
||||||
|
|
||||||
|
def dispatch_request(self, dhid, doc_id):
|
||||||
|
self.form = self.form_class(dhid=dhid, document=doc_id)
|
||||||
|
self.get_context()
|
||||||
|
|
||||||
|
if self.form.validate_on_submit():
|
||||||
|
self.form.save()
|
||||||
|
messages.success('Edit document successfully!')
|
||||||
|
next_url = url_for('inventory.device_details', id=dhid)
|
||||||
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
|
self.context.update({'form': self.form, 'title': self.title})
|
||||||
|
return flask.render_template(self.template_name, **self.context)
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceDocumentDeleteView(View):
|
||||||
|
methods = ['GET']
|
||||||
|
decorators = [login_required]
|
||||||
|
template_name = 'inventory/device_detail.html'
|
||||||
|
form_class = DeviceDocumentForm
|
||||||
|
|
||||||
|
def dispatch_request(self, dhid, doc_id):
|
||||||
|
self.form = self.form_class(dhid=dhid, document=doc_id)
|
||||||
|
next_url = url_for('inventory.device_details', id=dhid)
|
||||||
|
try:
|
||||||
|
self.form.remove()
|
||||||
|
except Exception as err:
|
||||||
|
msg = "{}".format(err)
|
||||||
|
messages.error(msg)
|
||||||
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
|
msg = "Document removed successfully."
|
||||||
|
messages.success(msg)
|
||||||
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
|
|
||||||
class NewTradeDocumentView(GenericMixin):
|
class NewTradeDocumentView(GenericMixin):
|
||||||
methods = ['POST', 'GET']
|
methods = ['POST', 'GET']
|
||||||
decorators = [login_required]
|
decorators = [login_required]
|
||||||
|
@ -810,6 +895,27 @@ class NewTradeDocumentView(GenericMixin):
|
||||||
return flask.render_template(self.template_name, **self.context)
|
return flask.render_template(self.template_name, **self.context)
|
||||||
|
|
||||||
|
|
||||||
|
class EditTransferDocumentView(GenericMixin):
|
||||||
|
decorators = [login_required]
|
||||||
|
methods = ['POST', 'GET']
|
||||||
|
template_name = 'inventory/trade_document.html'
|
||||||
|
form_class = TradeDocumentForm
|
||||||
|
title = "Edit document"
|
||||||
|
|
||||||
|
def dispatch_request(self, lot_id, doc_id):
|
||||||
|
self.form = self.form_class(lot=lot_id, document=doc_id)
|
||||||
|
self.get_context()
|
||||||
|
|
||||||
|
if self.form.validate_on_submit():
|
||||||
|
self.form.save()
|
||||||
|
messages.success('Edit document successfully!')
|
||||||
|
next_url = url_for('inventory.lotdevicelist', lot_id=lot_id)
|
||||||
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
|
self.context.update({'form': self.form, 'title': self.title})
|
||||||
|
return flask.render_template(self.template_name, **self.context)
|
||||||
|
|
||||||
|
|
||||||
class NewTransferView(GenericMixin):
|
class NewTransferView(GenericMixin):
|
||||||
methods = ['POST', 'GET']
|
methods = ['POST', 'GET']
|
||||||
template_name = 'inventory/new_transfer.html'
|
template_name = 'inventory/new_transfer.html'
|
||||||
|
@ -1512,8 +1618,28 @@ devices.add_url_rule(
|
||||||
'/action/datawipe/add/', view_func=NewDataWipeView.as_view('datawipe_add')
|
'/action/datawipe/add/', view_func=NewDataWipeView.as_view('datawipe_add')
|
||||||
)
|
)
|
||||||
devices.add_url_rule(
|
devices.add_url_rule(
|
||||||
'/lot/<string:lot_id>/trade-document/add/',
|
'/device/<string:dhid>/document/add/',
|
||||||
view_func=NewTradeDocumentView.as_view('trade_document_add'),
|
view_func=NewDeviceDocumentView.as_view('device_document_add'),
|
||||||
|
)
|
||||||
|
devices.add_url_rule(
|
||||||
|
'/device/<string:dhid>/document/edit/<string:doc_id>',
|
||||||
|
view_func=EditDeviceDocumentView.as_view('device_document_edit'),
|
||||||
|
)
|
||||||
|
devices.add_url_rule(
|
||||||
|
'/device/<string:dhid>/document/del/<string:doc_id>',
|
||||||
|
view_func=DeviceDocumentDeleteView.as_view('device_document_del'),
|
||||||
|
)
|
||||||
|
devices.add_url_rule(
|
||||||
|
'/lot/<string:lot_id>/transfer-document/add/',
|
||||||
|
view_func=NewTradeDocumentView.as_view('transfer_document_add'),
|
||||||
|
)
|
||||||
|
devices.add_url_rule(
|
||||||
|
'/lot/<string:lot_id>/document/edit/<string:doc_id>',
|
||||||
|
view_func=EditTransferDocumentView.as_view('transfer_document_edit'),
|
||||||
|
)
|
||||||
|
devices.add_url_rule(
|
||||||
|
'/lot/<string:lot_id>/document/del/<string:doc_id>',
|
||||||
|
view_func=DocumentDeleteView.as_view('document_del'),
|
||||||
)
|
)
|
||||||
devices.add_url_rule('/device/', view_func=DeviceListView.as_view('devicelist'))
|
devices.add_url_rule('/device/', view_func=DeviceListView.as_view('devicelist'))
|
||||||
devices.add_url_rule(
|
devices.add_url_rule(
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
"""add document device
|
||||||
|
|
||||||
|
Revision ID: ac476b60d952
|
||||||
|
Revises: 4f33137586dd
|
||||||
|
Create Date: 2023-03-31 10:46:02.463007
|
||||||
|
|
||||||
|
"""
|
||||||
|
import citext
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import teal
|
||||||
|
from alembic import context, op
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'ac476b60d952'
|
||||||
|
down_revision = '4f33137586dd'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_inv():
|
||||||
|
INV = context.get_x_argument(as_dictionary=True).get('inventory')
|
||||||
|
if not INV:
|
||||||
|
raise ValueError("Inventory value is not specified")
|
||||||
|
return INV
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.create_table(
|
||||||
|
'device_document',
|
||||||
|
sa.Column(
|
||||||
|
'updated',
|
||||||
|
sa.TIMESTAMP(timezone=True),
|
||||||
|
server_default=sa.text('CURRENT_TIMESTAMP'),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
'created',
|
||||||
|
sa.TIMESTAMP(timezone=True),
|
||||||
|
server_default=sa.text('CURRENT_TIMESTAMP'),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
'id',
|
||||||
|
postgresql.UUID(as_uuid=True),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
'type',
|
||||||
|
citext.CIText(),
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
'date',
|
||||||
|
sa.DateTime(),
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
'id_document',
|
||||||
|
citext.CIText(),
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
'description',
|
||||||
|
citext.CIText(),
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column('owner_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||||
|
sa.Column('device_id', sa.BigInteger(), nullable=False),
|
||||||
|
sa.Column(
|
||||||
|
'file_name',
|
||||||
|
citext.CIText(),
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
'file_hash',
|
||||||
|
citext.CIText(),
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.Column(
|
||||||
|
'url',
|
||||||
|
citext.CIText(),
|
||||||
|
teal.db.URL(),
|
||||||
|
nullable=True,
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
['device_id'],
|
||||||
|
[f'{get_inv()}.device.id'],
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
['owner_id'],
|
||||||
|
['common.user.id'],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
schema=f'{get_inv()}',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
op.drop_table('device_document', schema=f'{get_inv()}')
|
|
@ -1232,6 +1232,13 @@ class Placeholder(Thing):
|
||||||
return 'Twin'
|
return 'Twin'
|
||||||
return 'Placeholder'
|
return 'Placeholder'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def documents(self):
|
||||||
|
docs = self.device.documents
|
||||||
|
if self.binding:
|
||||||
|
return docs.union(self.binding.documents)
|
||||||
|
return docs
|
||||||
|
|
||||||
|
|
||||||
class Computer(Device):
|
class Computer(Device):
|
||||||
"""A chassis with components inside that can be processed
|
"""A chassis with components inside that can be processed
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from flask_sqlalchemy import event
|
from flask_sqlalchemy import event
|
||||||
|
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
|
@ -16,18 +17,23 @@ class Thing(db.Model):
|
||||||
`schema.org's Thing class <https://schema.org/Thing>`_
|
`schema.org's Thing class <https://schema.org/Thing>`_
|
||||||
using only needed fields.
|
using only needed fields.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__abstract__ = True
|
__abstract__ = True
|
||||||
updated = db.Column(db.TIMESTAMP(timezone=True),
|
updated = db.Column(
|
||||||
nullable=False,
|
db.TIMESTAMP(timezone=True),
|
||||||
index=True,
|
nullable=False,
|
||||||
server_default=db.text('CURRENT_TIMESTAMP'))
|
index=True,
|
||||||
|
server_default=db.text('CURRENT_TIMESTAMP'),
|
||||||
|
)
|
||||||
updated.comment = """The last time Devicehub recorded a change for
|
updated.comment = """The last time Devicehub recorded a change for
|
||||||
this thing.
|
this thing.
|
||||||
"""
|
"""
|
||||||
created = db.Column(db.TIMESTAMP(timezone=True),
|
created = db.Column(
|
||||||
nullable=False,
|
db.TIMESTAMP(timezone=True),
|
||||||
index=True,
|
nullable=False,
|
||||||
server_default=db.text('CURRENT_TIMESTAMP'))
|
index=True,
|
||||||
|
server_default=db.text('CURRENT_TIMESTAMP'),
|
||||||
|
)
|
||||||
created.comment = """When Devicehub created this."""
|
created.comment = """When Devicehub created this."""
|
||||||
|
|
||||||
def __init__(self, **kwargs) -> None:
|
def __init__(self, **kwargs) -> None:
|
||||||
|
@ -36,11 +42,15 @@ class Thing(db.Model):
|
||||||
self.created = kwargs.get('created', datetime.now(timezone.utc))
|
self.created = kwargs.get('created', datetime.now(timezone.utc))
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
db.session.delete(self)
|
||||||
|
|
||||||
|
|
||||||
def update_object_timestamp(mapper, connection, thing_obj):
|
def update_object_timestamp(mapper, connection, thing_obj):
|
||||||
""" This function update the stamptime of field updated """
|
"""This function update the stamptime of field updated"""
|
||||||
thing_obj.updated = datetime.now(timezone.utc)
|
thing_obj.updated = datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
def listener_reset_field_updated_in_actual_time(thing_obj):
|
def listener_reset_field_updated_in_actual_time(thing_obj):
|
||||||
""" This function launch a event than listen like a signal when some object is saved """
|
"""This function launch a event than listen like a signal when some object is saved"""
|
||||||
event.listen(thing_obj, 'before_update', update_object_timestamp, propagate=True)
|
event.listen(thing_obj, 'before_update', update_object_timestamp, propagate=True)
|
||||||
|
|
|
@ -65,6 +65,10 @@
|
||||||
<a class="nav-link" href="{{ device.public_link }}" target="_blank">Web</a>
|
<a class="nav-link" href="{{ device.public_link }}" target="_blank">Web</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#documents">Documents</button>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#lots">Lots</button>
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#lots">Lots</button>
|
||||||
</li>
|
</li>
|
||||||
|
@ -196,6 +200,81 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-pane fade profile-overview" id="documents">
|
||||||
|
<div class="btn-group dropdown ml-1 mt-1" uib-dropdown="">
|
||||||
|
<a href="{{ url_for('inventory.device_document_add', dhid=placeholder.device.devicehub_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="YYYY-MM-DD hh:mm">Uploaded on</th>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for doc in placeholder.documents %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{% if doc.get_url() %}
|
||||||
|
<a href="{{ doc.get_url() }}" target="_blank">{{ doc.file_name}}</a>
|
||||||
|
{% else %}
|
||||||
|
{{ doc.file_name}}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ doc.created.strftime('%Y-%m-%d %H:%M')}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ url_for('inventory.device_document_edit', dhid=doc.device.dhid, doc_id=doc.id) }}" title="Edit document">
|
||||||
|
<i class="bi bi-pencil-square"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="javascript:javascript:void(0)" data-bs-toggle="modal" data-bs-target="#btnRemoveDocument{{ loop.index }}" title="Remove document">
|
||||||
|
<i class="bi bi-trash-fill"></i>
|
||||||
|
</a>
|
||||||
|
<div class="modal fade" id="btnRemoveDocument{{ loop.index }}" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Delete Document</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
Are you sure that you want to delete this Document?<br />
|
||||||
|
<strong>{{ doc.file_name }}</strong>
|
||||||
|
<p class="text-danger">
|
||||||
|
This action cannot be undone.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary-outline" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<a href="{{ url_for('inventory.device_document_del', dhid=doc.device.dhid, doc_id=doc.id) }}" type="button" class="btn btn-danger">
|
||||||
|
Delete it!
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane fade profile-overview" id="status">
|
<div class="tab-pane fade profile-overview" id="status">
|
||||||
<h5 class="card-title">Status Details</h5>
|
<h5 class="card-title">Status Details</h5>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
{% extends "ereuse_devicehub/base_site.html" %}
|
||||||
|
{% block main %}
|
||||||
|
|
||||||
|
<div class="pagetitle">
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<nav>
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="#TODO-lot-list">Inventory</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="#TODO-lot-list">Device {{ form._device.dhid }}</a></li>
|
||||||
|
<li class="breadcrumb-item">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>
|
||||||
|
|
||||||
|
{% if form._obj or 1==2 %}
|
||||||
|
<form action="{{ url_for('inventory.device_document_edit', dhid=form._device.dhid, doc_id=form._obj.id) }}" method="post"
|
||||||
|
class="row g-3 needs-validation" enctype="multipart/form-data">
|
||||||
|
{% else %}
|
||||||
|
<form action="{{ url_for('inventory.device_document_add', dhid=form._device.dhid) }}" method="post"
|
||||||
|
class="row g-3 needs-validation" enctype="multipart/form-data">
|
||||||
|
{% endif %}
|
||||||
|
{{ form.csrf_token }}
|
||||||
|
{% for field in form %}
|
||||||
|
{% if field != form.csrf_token %}
|
||||||
|
<div>
|
||||||
|
{{ field.label(class_="form-label") }}
|
||||||
|
{{ field }}
|
||||||
|
<small class="text-muted">{{ field.description }}</small>
|
||||||
|
{% if field.errors %}
|
||||||
|
<p class="text-danger">
|
||||||
|
{% for error in field.errors %}
|
||||||
|
{{ error }}<br/>
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a href="{{ url_for('inventory.device_details', id=form._device.dhid) }}" class="btn btn-danger">Cancel</a>
|
||||||
|
<button class="btn btn-primary" type="submit">Save</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock main %}
|
|
@ -517,7 +517,7 @@
|
||||||
{% if lot and not lot.is_temporary %}
|
{% if lot and not lot.is_temporary %}
|
||||||
<div id="trade-documents-list" class="tab-pane fade trade-documents-list">
|
<div id="trade-documents-list" class="tab-pane fade trade-documents-list">
|
||||||
<div class="btn-group dropdown ml-1 mt-1" uib-dropdown="">
|
<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">
|
<a href="{{ url_for('inventory.transfer_document_add', lot_id=lot.id)}}" class="btn btn-primary">
|
||||||
<i class="bi bi-plus"></i>
|
<i class="bi bi-plus"></i>
|
||||||
Add new document
|
Add new document
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
|
@ -530,6 +530,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">File</th>
|
<th scope="col">File</th>
|
||||||
<th scope="col" data-type="date" data-format="YYYY-MM-DD hh:mm">Uploaded on</th>
|
<th scope="col" data-type="date" data-format="YYYY-MM-DD hh:mm">Uploaded on</th>
|
||||||
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -545,6 +546,43 @@
|
||||||
<td>
|
<td>
|
||||||
{{ doc.created.strftime('%Y-%m-%d %H:%M')}}
|
{{ doc.created.strftime('%Y-%m-%d %H:%M')}}
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ url_for('inventory.transfer_document_edit', lot_id=lot.id, doc_id=doc.id)}}" title="Edit document">
|
||||||
|
<i class="bi bi-pencil-square"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="javascript:javascript:void(0)" data-bs-toggle="modal" data-bs-target="#btnRemoveDocument{{ loop.index }}" title="Remove document">
|
||||||
|
<i class="bi bi-trash-fill"></i>
|
||||||
|
</a>
|
||||||
|
<div class="modal fade" id="btnRemoveDocument{{ loop.index }}" tabindex="-1" style="display: none;" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Delete Document</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
Are you sure that you want to delete this Document?<br />
|
||||||
|
<strong>{{ doc.file_name }}</strong>
|
||||||
|
<p class="text-danger">
|
||||||
|
This action cannot be undone.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary-outline" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<a href="{{ url_for('inventory.document_del', lot_id=lot.id, doc_id=doc.id) }}" type="button" class="btn btn-danger">
|
||||||
|
Delete it!
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% for doc in lot.trade.documents %}
|
{% for doc in lot.trade.documents %}
|
||||||
|
@ -559,6 +597,9 @@
|
||||||
<td>
|
<td>
|
||||||
{{ doc.created.strftime('%Y-%m-%d %H:%M')}}
|
{{ doc.created.strftime('%Y-%m-%d %H:%M')}}
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="javascript:void(0)"><i class="bi bi-trash-fill"></i></a>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -254,7 +254,7 @@
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu" aria-labelledby="btnSnapshot">
|
<ul class="dropdown-menu" aria-labelledby="btnSnapshot">
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ url_for('inventory.trade_document_add', lot_id=lot.id)}}" class="dropdown-item">
|
<a href="{{ url_for('inventory.transfer_document_add', lot_id=lot.id)}}" class="dropdown-item">
|
||||||
<i class="bi bi-plus"></i>
|
<i class="bi bi-plus"></i>
|
||||||
Add new document
|
Add new document
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
|
|
|
@ -30,8 +30,13 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form action="{{ url_for('inventory.trade_document_add', lot_id=form._lot.id) }}" method="post"
|
{% if form._obj %}
|
||||||
|
<form action="{{ url_for('inventory.transfer_document_edit', lot_id=form._lot.id, doc_id=form._obj.id) }}" method="post"
|
||||||
class="row g-3 needs-validation" enctype="multipart/form-data">
|
class="row g-3 needs-validation" enctype="multipart/form-data">
|
||||||
|
{% else %}
|
||||||
|
<form action="{{ url_for('inventory.transfer_document_add', lot_id=form._lot.id) }}" method="post"
|
||||||
|
class="row g-3 needs-validation" enctype="multipart/form-data">
|
||||||
|
{% endif %}
|
||||||
{{ form.csrf_token }}
|
{{ form.csrf_token }}
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
{% if field != form.csrf_token %}
|
{% if field != form.csrf_token %}
|
||||||
|
|
|
@ -55,6 +55,9 @@ def test_api_docs(client: Client):
|
||||||
'/inventory/device/add/',
|
'/inventory/device/add/',
|
||||||
'/inventory/device/{id}/',
|
'/inventory/device/{id}/',
|
||||||
'/inventory/device/{dhid}/binding/',
|
'/inventory/device/{dhid}/binding/',
|
||||||
|
'/inventory/device/{dhid}/document/del/{doc_id}',
|
||||||
|
'/inventory/device/{dhid}/document/edit/{doc_id}',
|
||||||
|
'/inventory/device/{dhid}/document/add/',
|
||||||
'/inventory/device/erasure/',
|
'/inventory/device/erasure/',
|
||||||
'/inventory/device/erasure/{orphans}/',
|
'/inventory/device/erasure/{orphans}/',
|
||||||
'/inventory/all/device/',
|
'/inventory/all/device/',
|
||||||
|
@ -66,13 +69,15 @@ def test_api_docs(client: Client):
|
||||||
'/inventory/lot/{lot_id}/device/add/',
|
'/inventory/lot/{lot_id}/device/add/',
|
||||||
'/inventory/lot/{lot_id}/deliverynote/',
|
'/inventory/lot/{lot_id}/deliverynote/',
|
||||||
'/inventory/lot/{lot_id}/receivernote/',
|
'/inventory/lot/{lot_id}/receivernote/',
|
||||||
'/inventory/lot/{lot_id}/trade-document/add/',
|
'/inventory/lot/{lot_id}/transfer-document/add/',
|
||||||
'/inventory/lot/{lot_id}/transfer/{type_id}/',
|
'/inventory/lot/{lot_id}/transfer/{type_id}/',
|
||||||
'/inventory/lot/{lot_id}/opentransfer/',
|
'/inventory/lot/{lot_id}/opentransfer/',
|
||||||
'/inventory/lot/{lot_id}/transfer/',
|
'/inventory/lot/{lot_id}/transfer/',
|
||||||
'/inventory/lot/transfer/{type_id}/',
|
'/inventory/lot/transfer/{type_id}/',
|
||||||
'/inventory/lot/{lot_id}/upload-snapshot/',
|
'/inventory/lot/{lot_id}/upload-snapshot/',
|
||||||
'/inventory/lot/{lot_id}/customerdetails/',
|
'/inventory/lot/{lot_id}/customerdetails/',
|
||||||
|
'/inventory/lot/{lot_id}/document/edit/{doc_id}',
|
||||||
|
'/inventory/lot/{lot_id}/document/del/{doc_id}',
|
||||||
'/inventory/snapshots/{snapshot_uuid}/',
|
'/inventory/snapshots/{snapshot_uuid}/',
|
||||||
'/inventory/snapshots/',
|
'/inventory/snapshots/',
|
||||||
'/inventory/tag/devices/{dhid}/add/',
|
'/inventory/tag/devices/{dhid}/add/',
|
||||||
|
|
|
@ -2468,7 +2468,7 @@ def test_bug_3831_documents(user3: UserClientFlask):
|
||||||
lot = Lot.query.filter_by(name=lot_name).one()
|
lot = Lot.query.filter_by(name=lot_name).one()
|
||||||
|
|
||||||
lot_id = lot.id
|
lot_id = lot.id
|
||||||
uri = f'/inventory/lot/{lot_id}/trade-document/add/'
|
uri = f'/inventory/lot/{lot_id}/transfer-document/add/'
|
||||||
body, status = user3.get(uri)
|
body, status = user3.get(uri)
|
||||||
txt = 'Error, this lot is not a transfer lot.'
|
txt = 'Error, this lot is not a transfer lot.'
|
||||||
|
|
||||||
|
@ -2486,7 +2486,7 @@ def test_bug_3831_documents(user3: UserClientFlask):
|
||||||
assert 'Incoming Lot' in body
|
assert 'Incoming Lot' in body
|
||||||
|
|
||||||
lot_id = Lot.query.all()[1].id
|
lot_id = Lot.query.all()[1].id
|
||||||
uri = f'/inventory/lot/{lot_id}/trade-document/add/'
|
uri = f'/inventory/lot/{lot_id}/transfer-document/add/'
|
||||||
body, status = user3.get(uri)
|
body, status = user3.get(uri)
|
||||||
|
|
||||||
b_file = b'1234567890'
|
b_file = b'1234567890'
|
||||||
|
@ -2502,12 +2502,12 @@ def test_bug_3831_documents(user3: UserClientFlask):
|
||||||
'file': file_upload,
|
'file': file_upload,
|
||||||
}
|
}
|
||||||
|
|
||||||
uri = f'/inventory/lot/{lot_id}/trade-document/add/'
|
uri = f'/inventory/lot/{lot_id}/transfer-document/add/'
|
||||||
body, status = user3.post(uri, data=data, content_type="multipart/form-data")
|
body, status = user3.post(uri, data=data, content_type="multipart/form-data")
|
||||||
assert status == '200 OK'
|
assert status == '200 OK'
|
||||||
|
|
||||||
# Second document
|
# Second document
|
||||||
uri = f'/inventory/lot/{lot_id}/trade-document/add/'
|
uri = f'/inventory/lot/{lot_id}/transfer-document/add/'
|
||||||
file_upload = (BytesIO(b_file), file_name)
|
file_upload = (BytesIO(b_file), file_name)
|
||||||
data['file'] = file_upload
|
data['file'] = file_upload
|
||||||
data['csrf_token'] = generate_csrf()
|
data['csrf_token'] = generate_csrf()
|
||||||
|
@ -2774,3 +2774,82 @@ def test_reliable_device(user3: UserClientFlask):
|
||||||
assert Snapshot.query.first() == snapshot
|
assert Snapshot.query.first() == snapshot
|
||||||
assert len(snapshot.device.components) == 8
|
assert len(snapshot.device.components) == 8
|
||||||
assert len(snapshot.device.actions) == 7
|
assert len(snapshot.device.actions) == 7
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_add_device_document(user3: UserClientFlask):
|
||||||
|
snapshot = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
device = Device.query.filter_by(devicehub_id=snapshot.device.dhid).one()
|
||||||
|
uri = '/inventory/device/{}/document/add/'.format(device.dhid)
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
name = "doc1.pdf"
|
||||||
|
url = "https://www.usody.com/"
|
||||||
|
file_name = (BytesIO(b'1234567890'), name)
|
||||||
|
data = {
|
||||||
|
'url': url,
|
||||||
|
'file_name': file_name,
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
}
|
||||||
|
|
||||||
|
user3.post(uri, data=data, content_type="multipart/form-data")
|
||||||
|
assert device.documents[0].file_name == name
|
||||||
|
assert device.documents[0].url.to_text() == url
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_edit_device_document(user3: UserClientFlask):
|
||||||
|
snapshot = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
device = Device.query.filter_by(devicehub_id=snapshot.device.dhid).one()
|
||||||
|
uri = '/inventory/device/{}/document/add/'.format(device.dhid)
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
name = "doc1.pdf"
|
||||||
|
url = "https://www.usody.com/"
|
||||||
|
file_name = (BytesIO(b'1234567890'), name)
|
||||||
|
data = {
|
||||||
|
'url': url,
|
||||||
|
'file_name': file_name,
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
}
|
||||||
|
|
||||||
|
user3.post(uri, data=data, content_type="multipart/form-data")
|
||||||
|
|
||||||
|
doc_id = str(device.documents[0].id)
|
||||||
|
uri = '/inventory/device/{}/document/edit/{}'.format(device.dhid, doc_id)
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
data['url'] = "https://www.ereuse.org/"
|
||||||
|
data['csrf_token'] = generate_csrf()
|
||||||
|
data['file_name'] = (BytesIO(b'1234567890'), name)
|
||||||
|
|
||||||
|
user3.post(uri, data=data, content_type="multipart/form-data")
|
||||||
|
assert device.documents[0].file_name == name
|
||||||
|
assert device.documents[0].url.to_text() == data['url']
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_delete_device_document(user3: UserClientFlask):
|
||||||
|
snapshot = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
device = Device.query.filter_by(devicehub_id=snapshot.device.dhid).one()
|
||||||
|
uri = '/inventory/device/{}/document/add/'.format(device.dhid)
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
name = "doc1.pdf"
|
||||||
|
url = "https://www.usody.com/"
|
||||||
|
file_name = (BytesIO(b'1234567890'), name)
|
||||||
|
data = {
|
||||||
|
'url': url,
|
||||||
|
'file_name': file_name,
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
}
|
||||||
|
|
||||||
|
user3.post(uri, data=data, content_type="multipart/form-data")
|
||||||
|
|
||||||
|
doc_id = str(device.documents[0].id)
|
||||||
|
uri = '/inventory/device/{}/document/del/{}'.format(device.dhid, doc_id)
|
||||||
|
user3.get(uri)
|
||||||
|
assert len(device.documents) == 0
|
||||||
|
|
Reference in New Issue