Merge remote-tracking branch 'MyRepo/testing' into feature/confirm-trade-changes
This commit is contained in:
commit
cb4f12eb1e
|
@ -2,7 +2,10 @@ from inspect import isclass
|
||||||
from typing import Dict, Iterable, Type, Union
|
from typing import Dict, Iterable, Type, Union
|
||||||
|
|
||||||
from ereuse_utils.test import JSON, Res
|
from ereuse_utils.test import JSON, Res
|
||||||
from teal.client import Client as TealClient, Query, Status
|
from flask.testing import FlaskClient
|
||||||
|
from flask_wtf.csrf import generate_csrf
|
||||||
|
from teal.client import Client as TealClient
|
||||||
|
from teal.client import Query, Status
|
||||||
from werkzeug.exceptions import HTTPException
|
from werkzeug.exceptions import HTTPException
|
||||||
|
|
||||||
from ereuse_devicehub.resources import models, schemas
|
from ereuse_devicehub.resources import models, schemas
|
||||||
|
@ -13,110 +16,156 @@ ResourceLike = Union[Type[Union[models.Thing, schemas.Thing]], str]
|
||||||
class Client(TealClient):
|
class Client(TealClient):
|
||||||
"""A client suited for Devicehub main usage."""
|
"""A client suited for Devicehub main usage."""
|
||||||
|
|
||||||
def __init__(self, application,
|
def __init__(
|
||||||
response_wrapper=None,
|
self,
|
||||||
use_cookies=False,
|
application,
|
||||||
allow_subdomain_redirects=False):
|
response_wrapper=None,
|
||||||
super().__init__(application, response_wrapper, use_cookies, allow_subdomain_redirects)
|
use_cookies=False,
|
||||||
|
allow_subdomain_redirects=False,
|
||||||
|
):
|
||||||
|
super().__init__(
|
||||||
|
application, response_wrapper, use_cookies, allow_subdomain_redirects
|
||||||
|
)
|
||||||
|
|
||||||
def open(self,
|
def open(
|
||||||
uri: str,
|
self,
|
||||||
res: ResourceLike = None,
|
uri: str,
|
||||||
status: Status = 200,
|
res: ResourceLike = None,
|
||||||
query: Query = tuple(),
|
status: Status = 200,
|
||||||
accept=JSON,
|
query: Query = tuple(),
|
||||||
content_type=JSON,
|
accept=JSON,
|
||||||
item=None,
|
content_type=JSON,
|
||||||
headers: dict = None,
|
item=None,
|
||||||
token: str = None,
|
headers: dict = None,
|
||||||
**kw) -> Res:
|
token: str = None,
|
||||||
|
**kw,
|
||||||
|
) -> Res:
|
||||||
if isclass(res) and issubclass(res, (models.Thing, schemas.Thing)):
|
if isclass(res) and issubclass(res, (models.Thing, schemas.Thing)):
|
||||||
res = res.t
|
res = res.t
|
||||||
return super().open(uri, res, status, query, accept, content_type, item, headers, token,
|
return super().open(
|
||||||
**kw)
|
uri, res, status, query, accept, content_type, item, headers, token, **kw
|
||||||
|
)
|
||||||
|
|
||||||
def get(self,
|
def get(
|
||||||
uri: str = '',
|
self,
|
||||||
res: ResourceLike = None,
|
uri: str = '',
|
||||||
query: Query = tuple(),
|
res: ResourceLike = None,
|
||||||
status: Status = 200,
|
query: Query = tuple(),
|
||||||
item: Union[int, str] = None,
|
status: Status = 200,
|
||||||
accept: str = JSON,
|
item: Union[int, str] = None,
|
||||||
headers: dict = None,
|
accept: str = JSON,
|
||||||
token: str = None,
|
headers: dict = None,
|
||||||
**kw) -> Res:
|
token: str = None,
|
||||||
|
**kw,
|
||||||
|
) -> Res:
|
||||||
return super().get(uri, res, query, status, item, accept, headers, token, **kw)
|
return super().get(uri, res, query, status, item, accept, headers, token, **kw)
|
||||||
|
|
||||||
def post(self,
|
def post(
|
||||||
data: str or dict,
|
self,
|
||||||
uri: str = '',
|
data: str or dict,
|
||||||
res: ResourceLike = None,
|
uri: str = '',
|
||||||
query: Query = tuple(),
|
res: ResourceLike = None,
|
||||||
status: Status = 201,
|
query: Query = tuple(),
|
||||||
content_type: str = JSON,
|
status: Status = 201,
|
||||||
accept: str = JSON,
|
content_type: str = JSON,
|
||||||
headers: dict = None,
|
accept: str = JSON,
|
||||||
token: str = None,
|
headers: dict = None,
|
||||||
**kw) -> Res:
|
token: str = None,
|
||||||
return super().post(data, uri, res, query, status, content_type, accept, headers, token,
|
**kw,
|
||||||
**kw)
|
) -> Res:
|
||||||
|
return super().post(
|
||||||
|
data, uri, res, query, status, content_type, accept, headers, token, **kw
|
||||||
|
)
|
||||||
|
|
||||||
def patch(self,
|
def patch(
|
||||||
data: str or dict,
|
self,
|
||||||
uri: str = '',
|
data: str or dict,
|
||||||
res: ResourceLike = None,
|
uri: str = '',
|
||||||
query: Query = tuple(),
|
res: ResourceLike = None,
|
||||||
item: Union[int, str] = None,
|
query: Query = tuple(),
|
||||||
status: Status = 200,
|
item: Union[int, str] = None,
|
||||||
content_type: str = JSON,
|
status: Status = 200,
|
||||||
accept: str = JSON,
|
content_type: str = JSON,
|
||||||
headers: dict = None,
|
accept: str = JSON,
|
||||||
token: str = None,
|
headers: dict = None,
|
||||||
**kw) -> Res:
|
token: str = None,
|
||||||
return super().patch(data, uri, res, query, item, status, content_type, accept, token,
|
**kw,
|
||||||
headers, **kw)
|
) -> Res:
|
||||||
|
return super().patch(
|
||||||
|
data,
|
||||||
|
uri,
|
||||||
|
res,
|
||||||
|
query,
|
||||||
|
item,
|
||||||
|
status,
|
||||||
|
content_type,
|
||||||
|
accept,
|
||||||
|
token,
|
||||||
|
headers,
|
||||||
|
**kw,
|
||||||
|
)
|
||||||
|
|
||||||
def put(self,
|
def put(
|
||||||
data: str or dict,
|
self,
|
||||||
uri: str = '',
|
data: str or dict,
|
||||||
res: ResourceLike = None,
|
uri: str = '',
|
||||||
query: Query = tuple(),
|
res: ResourceLike = None,
|
||||||
item: Union[int, str] = None,
|
query: Query = tuple(),
|
||||||
status: Status = 201,
|
item: Union[int, str] = None,
|
||||||
content_type: str = JSON,
|
status: Status = 201,
|
||||||
accept: str = JSON,
|
content_type: str = JSON,
|
||||||
headers: dict = None,
|
accept: str = JSON,
|
||||||
token: str = None,
|
headers: dict = None,
|
||||||
**kw) -> Res:
|
token: str = None,
|
||||||
return super().put(data, uri, res, query, item, status, content_type, accept, token,
|
**kw,
|
||||||
headers, **kw)
|
) -> Res:
|
||||||
|
return super().put(
|
||||||
|
data,
|
||||||
|
uri,
|
||||||
|
res,
|
||||||
|
query,
|
||||||
|
item,
|
||||||
|
status,
|
||||||
|
content_type,
|
||||||
|
accept,
|
||||||
|
token,
|
||||||
|
headers,
|
||||||
|
**kw,
|
||||||
|
)
|
||||||
|
|
||||||
def delete(self,
|
def delete(
|
||||||
uri: str = '',
|
self,
|
||||||
res: ResourceLike = None,
|
uri: str = '',
|
||||||
query: Query = tuple(),
|
res: ResourceLike = None,
|
||||||
status: Status = 204,
|
query: Query = tuple(),
|
||||||
item: Union[int, str] = None,
|
status: Status = 204,
|
||||||
accept: str = JSON,
|
item: Union[int, str] = None,
|
||||||
headers: dict = None,
|
accept: str = JSON,
|
||||||
token: str = None,
|
headers: dict = None,
|
||||||
**kw) -> Res:
|
token: str = None,
|
||||||
return super().delete(uri, res, query, status, item, accept, headers, token, **kw)
|
**kw,
|
||||||
|
) -> Res:
|
||||||
|
return super().delete(
|
||||||
|
uri, res, query, status, item, accept, headers, token, **kw
|
||||||
|
)
|
||||||
|
|
||||||
def login(self, email: str, password: str):
|
def login(self, email: str, password: str):
|
||||||
assert isinstance(email, str)
|
assert isinstance(email, str)
|
||||||
assert isinstance(password, str)
|
assert isinstance(password, str)
|
||||||
return self.post({'email': email, 'password': password}, '/users/login/', status=200)
|
return self.post(
|
||||||
|
{'email': email, 'password': password}, '/users/login/', status=200
|
||||||
|
)
|
||||||
|
|
||||||
def get_many(self,
|
def get_many(
|
||||||
res: ResourceLike,
|
self,
|
||||||
resources: Iterable[Union[dict, int]],
|
res: ResourceLike,
|
||||||
key: str = None,
|
resources: Iterable[Union[dict, int]],
|
||||||
**kw) -> Iterable[Union[Dict[str, object], str]]:
|
key: str = None,
|
||||||
|
**kw,
|
||||||
|
) -> Iterable[Union[Dict[str, object], str]]:
|
||||||
"""Like :meth:`.get` but with many resources."""
|
"""Like :meth:`.get` but with many resources."""
|
||||||
return (
|
return (
|
||||||
self.get(res=res, item=r[key] if key else r, **kw)[0]
|
self.get(res=res, item=r[key] if key else r, **kw)[0] for r in resources
|
||||||
for r in resources
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -126,33 +175,119 @@ class UserClient(Client):
|
||||||
It will automatically perform login on the first request.
|
It will automatically perform login on the first request.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, application,
|
def __init__(
|
||||||
email: str,
|
self,
|
||||||
password: str,
|
application,
|
||||||
response_wrapper=None,
|
email: str,
|
||||||
use_cookies=False,
|
password: str,
|
||||||
allow_subdomain_redirects=False):
|
response_wrapper=None,
|
||||||
super().__init__(application, response_wrapper, use_cookies, allow_subdomain_redirects)
|
use_cookies=False,
|
||||||
|
allow_subdomain_redirects=False,
|
||||||
|
):
|
||||||
|
super().__init__(
|
||||||
|
application, response_wrapper, use_cookies, allow_subdomain_redirects
|
||||||
|
)
|
||||||
self.email = email # type: str
|
self.email = email # type: str
|
||||||
self.password = password # type: str
|
self.password = password # type: str
|
||||||
self.user = None # type: dict
|
self.user = None # type: dict
|
||||||
|
|
||||||
def open(self,
|
def open(
|
||||||
uri: str,
|
self,
|
||||||
res: ResourceLike = None,
|
uri: str,
|
||||||
status: int or HTTPException = 200,
|
res: ResourceLike = None,
|
||||||
query: Query = tuple(),
|
status: int or HTTPException = 200,
|
||||||
accept=JSON,
|
query: Query = tuple(),
|
||||||
content_type=JSON,
|
accept=JSON,
|
||||||
item=None,
|
content_type=JSON,
|
||||||
headers: dict = None,
|
item=None,
|
||||||
token: str = None,
|
headers: dict = None,
|
||||||
**kw) -> Res:
|
token: str = None,
|
||||||
return super().open(uri, res, status, query, accept, content_type, item, headers,
|
**kw,
|
||||||
self.user['token'] if self.user else token, **kw)
|
) -> Res:
|
||||||
|
return super().open(
|
||||||
|
uri,
|
||||||
|
res,
|
||||||
|
status,
|
||||||
|
query,
|
||||||
|
accept,
|
||||||
|
content_type,
|
||||||
|
item,
|
||||||
|
headers,
|
||||||
|
self.user['token'] if self.user else token,
|
||||||
|
**kw,
|
||||||
|
)
|
||||||
|
|
||||||
# noinspection PyMethodOverriding
|
# noinspection PyMethodOverriding
|
||||||
def login(self):
|
def login(self):
|
||||||
response = super().login(self.email, self.password)
|
response = super().login(self.email, self.password)
|
||||||
self.user = response[0]
|
self.user = response[0]
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class UserClientFlask:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
application,
|
||||||
|
email: str,
|
||||||
|
password: str,
|
||||||
|
response_wrapper=None,
|
||||||
|
use_cookies=True,
|
||||||
|
follow_redirects=True,
|
||||||
|
):
|
||||||
|
self.email = email
|
||||||
|
self.password = password
|
||||||
|
self.follow_redirects = follow_redirects
|
||||||
|
self.user = None
|
||||||
|
|
||||||
|
self.client = FlaskClient(application, use_cookies=use_cookies)
|
||||||
|
self.client.get('/login/')
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'email': email,
|
||||||
|
'password': password,
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
}
|
||||||
|
body, status, headers = self.client.post(
|
||||||
|
'/login/', data=data, follow_redirects=True
|
||||||
|
)
|
||||||
|
self.headers = headers
|
||||||
|
body = next(body).decode("utf-8")
|
||||||
|
assert "Unassgined" in body
|
||||||
|
|
||||||
|
def get(
|
||||||
|
self,
|
||||||
|
uri='',
|
||||||
|
data=None,
|
||||||
|
follow_redirects=True,
|
||||||
|
content_type='text/html; charset=utf-8',
|
||||||
|
decode=True,
|
||||||
|
**kw,
|
||||||
|
):
|
||||||
|
|
||||||
|
body, status, headers = self.client.get(
|
||||||
|
uri, data=data, follow_redirects=follow_redirects, headers=self.headers
|
||||||
|
)
|
||||||
|
if decode:
|
||||||
|
body = next(body).decode("utf-8")
|
||||||
|
return (body, status)
|
||||||
|
|
||||||
|
def post(
|
||||||
|
self,
|
||||||
|
uri='',
|
||||||
|
data=None,
|
||||||
|
follow_redirects=True,
|
||||||
|
content_type='application/x-www-form-urlencoded',
|
||||||
|
decode=True,
|
||||||
|
**kw,
|
||||||
|
):
|
||||||
|
|
||||||
|
body, status, headers = self.client.post(
|
||||||
|
uri,
|
||||||
|
data=data,
|
||||||
|
follow_redirects=follow_redirects,
|
||||||
|
headers=self.headers,
|
||||||
|
content_type=content_type,
|
||||||
|
)
|
||||||
|
if decode:
|
||||||
|
body = next(body).decode("utf-8")
|
||||||
|
return (body, status)
|
||||||
|
|
|
@ -497,7 +497,7 @@ class TagDeviceForm(FlaskForm):
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
class NewActionForm(FlaskForm):
|
class ActionFormMix(FlaskForm):
|
||||||
name = StringField(
|
name = StringField(
|
||||||
'Name',
|
'Name',
|
||||||
[validators.length(max=50)],
|
[validators.length(max=50)],
|
||||||
|
@ -529,17 +529,23 @@ class NewActionForm(FlaskForm):
|
||||||
if not is_valid:
|
if not is_valid:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self._devices = OrderedSet()
|
if self.type.data in [None, '']:
|
||||||
if self.devices.data:
|
return False
|
||||||
devices = set(self.devices.data.split(","))
|
|
||||||
self._devices = OrderedSet(
|
|
||||||
Device.query.filter(Device.id.in_(devices))
|
|
||||||
.filter(Device.owner_id == g.user.id)
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
if not self._devices:
|
if not self.devices.data:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
self._devices = OrderedSet()
|
||||||
|
|
||||||
|
devices = set(self.devices.data.split(","))
|
||||||
|
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
|
return True
|
||||||
|
|
||||||
|
@ -572,7 +578,20 @@ class NewActionForm(FlaskForm):
|
||||||
return self.type.data
|
return self.type.data
|
||||||
|
|
||||||
|
|
||||||
class AllocateForm(NewActionForm):
|
class NewActionForm(ActionFormMix):
|
||||||
|
def validate(self, extra_validators=None):
|
||||||
|
is_valid = super().validate(extra_validators)
|
||||||
|
|
||||||
|
if not is_valid:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.type.data in ['Allocate', 'Deallocate', 'Trade', 'DataWipe']:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class AllocateForm(ActionFormMix):
|
||||||
start_time = DateField('Start time')
|
start_time = DateField('Start time')
|
||||||
end_time = DateField('End time')
|
end_time = DateField('End time')
|
||||||
final_user_code = StringField('Final user code', [validators.length(max=50)])
|
final_user_code = StringField('Final user code', [validators.length(max=50)])
|
||||||
|
@ -582,6 +601,9 @@ class AllocateForm(NewActionForm):
|
||||||
def validate(self, extra_validators=None):
|
def validate(self, extra_validators=None):
|
||||||
is_valid = super().validate(extra_validators)
|
is_valid = super().validate(extra_validators)
|
||||||
|
|
||||||
|
if self.type.data not in ['Allocate', 'Deallocate']:
|
||||||
|
return False
|
||||||
|
|
||||||
start_time = self.start_time.data
|
start_time = self.start_time.data
|
||||||
end_time = self.end_time.data
|
end_time = self.end_time.data
|
||||||
if start_time and end_time and end_time < start_time:
|
if start_time and end_time and end_time < start_time:
|
||||||
|
@ -650,7 +672,7 @@ class DataWipeDocumentForm(Form):
|
||||||
return self._obj
|
return self._obj
|
||||||
|
|
||||||
|
|
||||||
class DataWipeForm(NewActionForm):
|
class DataWipeForm(ActionFormMix):
|
||||||
document = FormField(DataWipeDocumentForm)
|
document = FormField(DataWipeDocumentForm)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
|
@ -677,7 +699,7 @@ class DataWipeForm(NewActionForm):
|
||||||
return self.instance
|
return self.instance
|
||||||
|
|
||||||
|
|
||||||
class TradeForm(NewActionForm):
|
class TradeForm(ActionFormMix):
|
||||||
user_from = StringField(
|
user_from = StringField(
|
||||||
'Supplier',
|
'Supplier',
|
||||||
[validators.Optional()],
|
[validators.Optional()],
|
||||||
|
@ -724,6 +746,9 @@ class TradeForm(NewActionForm):
|
||||||
email_from = self.user_from.data
|
email_from = self.user_from.data
|
||||||
email_to = self.user_to.data
|
email_to = self.user_to.data
|
||||||
|
|
||||||
|
if self.type.data != "Trade":
|
||||||
|
return False
|
||||||
|
|
||||||
if not self.confirm.data and not self.code.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"]
|
self.code.errors = ["If you don't want to confirm, you need a code"]
|
||||||
is_valid = False
|
is_valid = False
|
||||||
|
|
|
@ -7,10 +7,9 @@ import flask_weasyprint
|
||||||
from flask import Blueprint, g, make_response, request, url_for
|
from flask import Blueprint, g, make_response, request, url_for
|
||||||
from flask.views import View
|
from flask.views import View
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
from sqlalchemy import or_
|
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
from ereuse_devicehub import __version__, messages
|
from ereuse_devicehub import messages
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.inventory.forms import (
|
from ereuse_devicehub.inventory.forms import (
|
||||||
AllocateForm,
|
AllocateForm,
|
||||||
|
@ -31,35 +30,21 @@ from ereuse_devicehub.resources.documents.device_row import ActionRow, DeviceRow
|
||||||
from ereuse_devicehub.resources.hash_reports import insert_hash
|
from ereuse_devicehub.resources.hash_reports import insert_hash
|
||||||
from ereuse_devicehub.resources.lot.models import Lot
|
from ereuse_devicehub.resources.lot.models import Lot
|
||||||
from ereuse_devicehub.resources.tag.model import Tag
|
from ereuse_devicehub.resources.tag.model import Tag
|
||||||
|
from ereuse_devicehub.views import GenericMixView
|
||||||
|
|
||||||
devices = Blueprint('inventory', __name__, url_prefix='/inventory')
|
devices = Blueprint('inventory', __name__, url_prefix='/inventory')
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class GenericMixView(View):
|
|
||||||
def get_lots(self):
|
|
||||||
return (
|
|
||||||
Lot.query.outerjoin(Trade)
|
|
||||||
.filter(
|
|
||||||
or_(
|
|
||||||
Trade.user_from == g.user,
|
|
||||||
Trade.user_to == g.user,
|
|
||||||
Lot.owner_id == g.user.id,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.distinct()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceListMix(GenericMixView):
|
class DeviceListMix(GenericMixView):
|
||||||
decorators = [login_required]
|
|
||||||
template_name = 'inventory/device_list.html'
|
template_name = 'inventory/device_list.html'
|
||||||
|
|
||||||
def get_context(self, lot_id):
|
def get_context(self, lot_id):
|
||||||
|
super().get_context()
|
||||||
|
lots = self.context['lots']
|
||||||
form_filter = FilterForm()
|
form_filter = FilterForm()
|
||||||
filter_types = form_filter.search()
|
filter_types = form_filter.search()
|
||||||
lots = self.get_lots()
|
|
||||||
lot = None
|
lot = None
|
||||||
tags = (
|
tags = (
|
||||||
Tag.query.filter(Tag.owner_id == current_user.id)
|
Tag.query.filter(Tag.owner_id == current_user.id)
|
||||||
|
@ -105,21 +90,21 @@ class DeviceListMix(GenericMixView):
|
||||||
if action_devices:
|
if action_devices:
|
||||||
list_devices.extend([int(x) for x in action_devices.split(",")])
|
list_devices.extend([int(x) for x in action_devices.split(",")])
|
||||||
|
|
||||||
self.context = {
|
self.context.update(
|
||||||
'devices': devices,
|
{
|
||||||
'lots': lots,
|
'devices': devices,
|
||||||
'form_tag_device': TagDeviceForm(),
|
'form_tag_device': TagDeviceForm(),
|
||||||
'form_new_action': form_new_action,
|
'form_new_action': form_new_action,
|
||||||
'form_new_allocate': form_new_allocate,
|
'form_new_allocate': form_new_allocate,
|
||||||
'form_new_datawipe': form_new_datawipe,
|
'form_new_datawipe': form_new_datawipe,
|
||||||
'form_new_trade': form_new_trade,
|
'form_new_trade': form_new_trade,
|
||||||
'form_filter': form_filter,
|
'form_filter': form_filter,
|
||||||
'form_print_labels': PrintLabelsForm(),
|
'form_print_labels': PrintLabelsForm(),
|
||||||
'lot': lot,
|
'lot': lot,
|
||||||
'tags': tags,
|
'tags': tags,
|
||||||
'list_devices': list_devices,
|
'list_devices': list_devices,
|
||||||
'version': __version__,
|
}
|
||||||
}
|
)
|
||||||
|
|
||||||
return self.context
|
return self.context
|
||||||
|
|
||||||
|
@ -135,20 +120,20 @@ class DeviceDetailView(GenericMixView):
|
||||||
template_name = 'inventory/device_detail.html'
|
template_name = 'inventory/device_detail.html'
|
||||||
|
|
||||||
def dispatch_request(self, id):
|
def dispatch_request(self, id):
|
||||||
lots = self.get_lots()
|
self.get_context()
|
||||||
device = (
|
device = (
|
||||||
Device.query.filter(Device.owner_id == current_user.id)
|
Device.query.filter(Device.owner_id == current_user.id)
|
||||||
.filter(Device.devicehub_id == id)
|
.filter(Device.devicehub_id == id)
|
||||||
.one()
|
.one()
|
||||||
)
|
)
|
||||||
|
|
||||||
context = {
|
self.context.update(
|
||||||
'device': device,
|
{
|
||||||
'lots': lots,
|
'device': device,
|
||||||
'page_title': 'Device {}'.format(device.devicehub_id),
|
'page_title': 'Device {}'.format(device.devicehub_id),
|
||||||
'version': __version__,
|
}
|
||||||
}
|
)
|
||||||
return flask.render_template(self.template_name, **context)
|
return flask.render_template(self.template_name, **self.context)
|
||||||
|
|
||||||
|
|
||||||
class LotCreateView(GenericMixView):
|
class LotCreateView(GenericMixView):
|
||||||
|
@ -164,17 +149,17 @@ class LotCreateView(GenericMixView):
|
||||||
next_url = url_for('inventory.lotdevicelist', lot_id=form.id)
|
next_url = url_for('inventory.lotdevicelist', lot_id=form.id)
|
||||||
return flask.redirect(next_url)
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
lots = self.get_lots()
|
self.get_context()
|
||||||
context = {
|
self.context.update(
|
||||||
'form': form,
|
{
|
||||||
'title': self.title,
|
'form': form,
|
||||||
'lots': lots,
|
'title': self.title,
|
||||||
'version': __version__,
|
}
|
||||||
}
|
)
|
||||||
return flask.render_template(self.template_name, **context)
|
return flask.render_template(self.template_name, **self.context)
|
||||||
|
|
||||||
|
|
||||||
class LotUpdateView(View):
|
class LotUpdateView(GenericMixView):
|
||||||
methods = ['GET', 'POST']
|
methods = ['GET', 'POST']
|
||||||
decorators = [login_required]
|
decorators = [login_required]
|
||||||
template_name = 'inventory/lot.html'
|
template_name = 'inventory/lot.html'
|
||||||
|
@ -187,14 +172,14 @@ class LotUpdateView(View):
|
||||||
next_url = url_for('inventory.lotdevicelist', lot_id=id)
|
next_url = url_for('inventory.lotdevicelist', lot_id=id)
|
||||||
return flask.redirect(next_url)
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
lots = Lot.query.filter(Lot.owner_id == current_user.id)
|
self.get_context()
|
||||||
context = {
|
self.context.update(
|
||||||
'form': form,
|
{
|
||||||
'title': self.title,
|
'form': form,
|
||||||
'lots': lots,
|
'title': self.title,
|
||||||
'version': __version__,
|
}
|
||||||
}
|
)
|
||||||
return flask.render_template(self.template_name, **context)
|
return flask.render_template(self.template_name, **self.context)
|
||||||
|
|
||||||
|
|
||||||
class LotDeleteView(View):
|
class LotDeleteView(View):
|
||||||
|
@ -221,24 +206,25 @@ class UploadSnapshotView(GenericMixView):
|
||||||
template_name = 'inventory/upload_snapshot.html'
|
template_name = 'inventory/upload_snapshot.html'
|
||||||
|
|
||||||
def dispatch_request(self, lot_id=None):
|
def dispatch_request(self, lot_id=None):
|
||||||
lots = self.get_lots()
|
self.get_context()
|
||||||
form = UploadSnapshotForm()
|
form = UploadSnapshotForm()
|
||||||
context = {
|
self.context.update(
|
||||||
'page_title': 'Upload Snapshot',
|
{
|
||||||
'lots': lots,
|
'page_title': 'Upload Snapshot',
|
||||||
'form': form,
|
'form': form,
|
||||||
'lot_id': lot_id,
|
'lot_id': lot_id,
|
||||||
'version': __version__,
|
}
|
||||||
}
|
)
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
snapshot = form.save(commit=False)
|
snapshot = form.save(commit=False)
|
||||||
if lot_id:
|
if lot_id:
|
||||||
|
lots = self.context['lots']
|
||||||
lot = lots.filter(Lot.id == lot_id).one()
|
lot = lots.filter(Lot.id == lot_id).one()
|
||||||
lot.devices.add(snapshot.device)
|
lot.devices.add(snapshot.device)
|
||||||
db.session.add(lot)
|
db.session.add(lot)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return flask.render_template(self.template_name, **context)
|
return flask.render_template(self.template_name, **self.context)
|
||||||
|
|
||||||
|
|
||||||
class DeviceCreateView(GenericMixView):
|
class DeviceCreateView(GenericMixView):
|
||||||
|
@ -247,20 +233,21 @@ class DeviceCreateView(GenericMixView):
|
||||||
template_name = 'inventory/device_create.html'
|
template_name = 'inventory/device_create.html'
|
||||||
|
|
||||||
def dispatch_request(self, lot_id=None):
|
def dispatch_request(self, lot_id=None):
|
||||||
lots = self.get_lots()
|
self.get_context()
|
||||||
form = NewDeviceForm()
|
form = NewDeviceForm()
|
||||||
context = {
|
self.context.update(
|
||||||
'page_title': 'New Device',
|
{
|
||||||
'lots': lots,
|
'page_title': 'New Device',
|
||||||
'form': form,
|
'form': form,
|
||||||
'lot_id': lot_id,
|
'lot_id': lot_id,
|
||||||
'version': __version__,
|
}
|
||||||
}
|
)
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
snapshot = form.save(commit=False)
|
snapshot = form.save(commit=False)
|
||||||
next_url = url_for('inventory.devicelist')
|
next_url = url_for('inventory.devicelist')
|
||||||
if lot_id:
|
if lot_id:
|
||||||
next_url = url_for('inventory.lotdevicelist', lot_id=lot_id)
|
next_url = url_for('inventory.lotdevicelist', lot_id=lot_id)
|
||||||
|
lots = self.context['lots']
|
||||||
lot = lots.filter(Lot.id == lot_id).one()
|
lot = lots.filter(Lot.id == lot_id).one()
|
||||||
lot.devices.add(snapshot.device)
|
lot.devices.add(snapshot.device)
|
||||||
db.session.add(lot)
|
db.session.add(lot)
|
||||||
|
@ -269,7 +256,7 @@ class DeviceCreateView(GenericMixView):
|
||||||
messages.success('Device "{}" created successfully!'.format(form.type.data))
|
messages.success('Device "{}" created successfully!'.format(form.type.data))
|
||||||
return flask.redirect(next_url)
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
return flask.render_template(self.template_name, **context)
|
return flask.render_template(self.template_name, **self.context)
|
||||||
|
|
||||||
|
|
||||||
class TagLinkDeviceView(View):
|
class TagLinkDeviceView(View):
|
||||||
|
@ -285,13 +272,13 @@ class TagLinkDeviceView(View):
|
||||||
return flask.redirect(request.referrer)
|
return flask.redirect(request.referrer)
|
||||||
|
|
||||||
|
|
||||||
class TagUnlinkDeviceView(View):
|
class TagUnlinkDeviceView(GenericMixView):
|
||||||
methods = ['POST', 'GET']
|
methods = ['POST', 'GET']
|
||||||
decorators = [login_required]
|
decorators = [login_required]
|
||||||
template_name = 'inventory/tag_unlink_device.html'
|
template_name = 'inventory/tag_unlink_device.html'
|
||||||
|
|
||||||
def dispatch_request(self, id):
|
def dispatch_request(self, id):
|
||||||
lots = Lot.query.filter(Lot.owner_id == current_user.id)
|
self.get_context()
|
||||||
form = TagDeviceForm(delete=True, device=id)
|
form = TagDeviceForm(delete=True, device=id)
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
form.remove()
|
form.remove()
|
||||||
|
@ -299,14 +286,15 @@ class TagUnlinkDeviceView(View):
|
||||||
next_url = url_for('inventory.devicelist')
|
next_url = url_for('inventory.devicelist')
|
||||||
return flask.redirect(next_url)
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
return flask.render_template(
|
self.context.update(
|
||||||
self.template_name,
|
{
|
||||||
form=form,
|
'form': form,
|
||||||
lots=lots,
|
'referrer': request.referrer,
|
||||||
referrer=request.referrer,
|
}
|
||||||
version=__version__,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return flask.render_template(self.template_name, **self.context)
|
||||||
|
|
||||||
|
|
||||||
class NewActionView(View):
|
class NewActionView(View):
|
||||||
methods = ['POST']
|
methods = ['POST']
|
||||||
|
@ -315,16 +303,19 @@ class NewActionView(View):
|
||||||
|
|
||||||
def dispatch_request(self):
|
def dispatch_request(self):
|
||||||
self.form = self.form_class()
|
self.form = self.form_class()
|
||||||
|
next_url = self.get_next_url()
|
||||||
|
|
||||||
if self.form.validate_on_submit():
|
if self.form.validate_on_submit():
|
||||||
self.form.save()
|
self.form.save()
|
||||||
messages.success(
|
messages.success(
|
||||||
'Action "{}" created successfully!'.format(self.form.type.data)
|
'Action "{}" created successfully!'.format(self.form.type.data)
|
||||||
)
|
)
|
||||||
|
|
||||||
next_url = self.get_next_url()
|
next_url = self.get_next_url()
|
||||||
return flask.redirect(next_url)
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
|
messages.error('Action {} error!'.format(self.form.type.data))
|
||||||
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
def get_next_url(self):
|
def get_next_url(self):
|
||||||
lot_id = self.form.lot.data
|
lot_id = self.form.lot.data
|
||||||
|
|
||||||
|
@ -350,10 +341,12 @@ class NewAllocateView(NewActionView, DeviceListMix):
|
||||||
next_url = self.get_next_url()
|
next_url = self.get_next_url()
|
||||||
return flask.redirect(next_url)
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
lot_id = self.form.lot.data
|
messages.error('Action {} error!'.format(self.form.type.data))
|
||||||
self.get_context(lot_id)
|
for k, v in self.form.errors.items():
|
||||||
self.context['form_new_allocate'] = self.form
|
value = ';'.join(v)
|
||||||
return flask.render_template(self.template_name, **self.context)
|
messages.error('Action Error {key}: {value}!'.format(key=k, value=value))
|
||||||
|
next_url = self.get_next_url()
|
||||||
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
|
|
||||||
class NewDataWipeView(NewActionView, DeviceListMix):
|
class NewDataWipeView(NewActionView, DeviceListMix):
|
||||||
|
@ -372,10 +365,9 @@ class NewDataWipeView(NewActionView, DeviceListMix):
|
||||||
next_url = self.get_next_url()
|
next_url = self.get_next_url()
|
||||||
return flask.redirect(next_url)
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
lot_id = self.form.lot.data
|
messages.error('Action {} error!'.format(self.form.type.data))
|
||||||
self.get_context(lot_id)
|
next_url = self.get_next_url()
|
||||||
self.context['form_new_datawipe'] = self.form
|
return flask.redirect(next_url)
|
||||||
return flask.render_template(self.template_name, **self.context)
|
|
||||||
|
|
||||||
|
|
||||||
class NewTradeView(NewActionView, DeviceListMix):
|
class NewTradeView(NewActionView, DeviceListMix):
|
||||||
|
@ -394,10 +386,9 @@ class NewTradeView(NewActionView, DeviceListMix):
|
||||||
next_url = self.get_next_url()
|
next_url = self.get_next_url()
|
||||||
return flask.redirect(next_url)
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
lot_id = self.form.lot.data
|
messages.error('Action {} error!'.format(self.form.type.data))
|
||||||
self.get_context(lot_id)
|
next_url = self.get_next_url()
|
||||||
self.context['form_new_trade'] = self.form
|
return flask.redirect(next_url)
|
||||||
return flask.render_template(self.template_name, **self.context)
|
|
||||||
|
|
||||||
|
|
||||||
class NewTradeDocumentView(View):
|
class NewTradeDocumentView(View):
|
||||||
|
@ -409,6 +400,7 @@ class NewTradeDocumentView(View):
|
||||||
|
|
||||||
def dispatch_request(self, lot_id):
|
def dispatch_request(self, lot_id):
|
||||||
self.form = self.form_class(lot=lot_id)
|
self.form = self.form_class(lot=lot_id)
|
||||||
|
self.get_context()
|
||||||
|
|
||||||
if self.form.validate_on_submit():
|
if self.form.validate_on_submit():
|
||||||
self.form.save()
|
self.form.save()
|
||||||
|
@ -416,9 +408,8 @@ class NewTradeDocumentView(View):
|
||||||
next_url = url_for('inventory.lotdevicelist', lot_id=lot_id)
|
next_url = url_for('inventory.lotdevicelist', lot_id=lot_id)
|
||||||
return flask.redirect(next_url)
|
return flask.redirect(next_url)
|
||||||
|
|
||||||
return flask.render_template(
|
self.context.update({'form': self.form, 'title': self.title})
|
||||||
self.template_name, form=self.form, title=self.title, version=__version__
|
return flask.render_template(self.template_name, **self.context)
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ExportsView(View):
|
class ExportsView(View):
|
||||||
|
|
|
@ -64,10 +64,7 @@ class PrintLabelsForm(FlaskForm):
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
|
|
||||||
# print only tags that are DHID
|
if not self._devices:
|
||||||
dhids = [x.devicehub_id for x in self._devices]
|
return False
|
||||||
self._tags = (
|
|
||||||
Tag.query.filter(Tag.owner_id == g.user.id).filter(Tag.id.in_(dhids)).all()
|
|
||||||
)
|
|
||||||
|
|
||||||
return is_valid
|
return is_valid
|
||||||
|
|
|
@ -27,7 +27,7 @@ class TagListView(View):
|
||||||
context = {
|
context = {
|
||||||
'lots': lots,
|
'lots': lots,
|
||||||
'tags': tags,
|
'tags': tags,
|
||||||
'page_title': 'Tags Management',
|
'page_title': 'Unique Identifiers Management',
|
||||||
'version': __version__,
|
'version': __version__,
|
||||||
}
|
}
|
||||||
return flask.render_template(self.template_name, **context)
|
return flask.render_template(self.template_name, **context)
|
||||||
|
@ -102,7 +102,7 @@ class PrintLabelsView(View):
|
||||||
form = PrintLabelsForm()
|
form = PrintLabelsForm()
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
context['form'] = form
|
context['form'] = form
|
||||||
context['tags'] = form._tags
|
context['devices'] = form._devices
|
||||||
return flask.render_template(self.template_name, **context)
|
return flask.render_template(self.template_name, **context)
|
||||||
else:
|
else:
|
||||||
messages.error('Error you need select one or more devices')
|
messages.error('Error you need select one or more devices')
|
||||||
|
|
|
@ -1,20 +1,30 @@
|
||||||
import pathlib
|
|
||||||
import copy
|
import copy
|
||||||
|
import pathlib
|
||||||
import time
|
import time
|
||||||
from flask import g
|
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
from typing import Dict, List, Set
|
from typing import Dict, List, Set
|
||||||
from flask_sqlalchemy import event
|
|
||||||
|
|
||||||
from boltons import urlutils
|
from boltons import urlutils
|
||||||
from citext import CIText
|
from citext import CIText
|
||||||
from ereuse_utils.naming import HID_CONVERSION_DOC, Naming
|
from ereuse_utils.naming import HID_CONVERSION_DOC, Naming
|
||||||
|
from flask import g
|
||||||
|
from flask_sqlalchemy import event
|
||||||
from more_itertools import unique_everseen
|
from more_itertools import unique_everseen
|
||||||
from sqlalchemy import BigInteger, Boolean, Column, Enum as DBEnum, Float, ForeignKey, Integer, \
|
from sqlalchemy import BigInteger, Boolean, Column
|
||||||
Sequence, SmallInteger, Unicode, inspect, text
|
from sqlalchemy import Enum as DBEnum
|
||||||
|
from sqlalchemy import (
|
||||||
|
Float,
|
||||||
|
ForeignKey,
|
||||||
|
Integer,
|
||||||
|
Sequence,
|
||||||
|
SmallInteger,
|
||||||
|
Unicode,
|
||||||
|
inspect,
|
||||||
|
text,
|
||||||
|
)
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
from sqlalchemy.ext.hybrid import hybrid_property
|
from sqlalchemy.ext.hybrid import hybrid_property
|
||||||
|
@ -22,19 +32,41 @@ from sqlalchemy.orm import ColumnProperty, backref, relationship, validates
|
||||||
from sqlalchemy.util import OrderedSet
|
from sqlalchemy.util import OrderedSet
|
||||||
from sqlalchemy_utils import ColorType
|
from sqlalchemy_utils import ColorType
|
||||||
from stdnum import imei, meid
|
from stdnum import imei, meid
|
||||||
from teal.db import CASCADE_DEL, POLYMORPHIC_ID, POLYMORPHIC_ON, ResourceNotFound, URL, \
|
from teal.db import (
|
||||||
check_lower, check_range, IntEnum
|
CASCADE_DEL,
|
||||||
|
POLYMORPHIC_ID,
|
||||||
|
POLYMORPHIC_ON,
|
||||||
|
URL,
|
||||||
|
IntEnum,
|
||||||
|
ResourceNotFound,
|
||||||
|
check_lower,
|
||||||
|
check_range,
|
||||||
|
)
|
||||||
from teal.enums import Layouts
|
from teal.enums import Layouts
|
||||||
from teal.marshmallow import ValidationError
|
from teal.marshmallow import ValidationError
|
||||||
from teal.resource import url_for_resource
|
from teal.resource import url_for_resource
|
||||||
|
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.resources.utils import hashcode
|
|
||||||
from ereuse_devicehub.resources.enums import BatteryTechnology, CameraFacing, ComputerChassis, \
|
|
||||||
DataStorageInterface, DisplayTech, PrinterTechnology, RamFormat, RamInterface, Severity, TransferState
|
|
||||||
from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing, listener_reset_field_updated_in_actual_time
|
|
||||||
from ereuse_devicehub.resources.user.models import User
|
|
||||||
from ereuse_devicehub.resources.device.metrics import Metrics
|
from ereuse_devicehub.resources.device.metrics import Metrics
|
||||||
|
from ereuse_devicehub.resources.enums import (
|
||||||
|
BatteryTechnology,
|
||||||
|
CameraFacing,
|
||||||
|
ComputerChassis,
|
||||||
|
DataStorageInterface,
|
||||||
|
DisplayTech,
|
||||||
|
PrinterTechnology,
|
||||||
|
RamFormat,
|
||||||
|
RamInterface,
|
||||||
|
Severity,
|
||||||
|
TransferState,
|
||||||
|
)
|
||||||
|
from ereuse_devicehub.resources.models import (
|
||||||
|
STR_SM_SIZE,
|
||||||
|
Thing,
|
||||||
|
listener_reset_field_updated_in_actual_time,
|
||||||
|
)
|
||||||
|
from ereuse_devicehub.resources.user.models import User
|
||||||
|
from ereuse_devicehub.resources.utils import hashcode
|
||||||
|
|
||||||
|
|
||||||
def create_code(context):
|
def create_code(context):
|
||||||
|
@ -58,17 +90,21 @@ class Device(Thing):
|
||||||
Devices can contain ``Components``, which are just a type of device
|
Devices can contain ``Components``, which are just a type of device
|
||||||
(it is a recursive relationship).
|
(it is a recursive relationship).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id = Column(BigInteger, Sequence('device_seq'), primary_key=True)
|
id = Column(BigInteger, Sequence('device_seq'), primary_key=True)
|
||||||
id.comment = """The identifier of the device for this database. Used only
|
id.comment = """The identifier of the device for this database. Used only
|
||||||
internally for software; users should not use this.
|
internally for software; users should not use this.
|
||||||
"""
|
"""
|
||||||
type = Column(Unicode(STR_SM_SIZE), nullable=False)
|
type = Column(Unicode(STR_SM_SIZE), nullable=False)
|
||||||
hid = Column(Unicode(), check_lower('hid'), unique=False)
|
hid = Column(Unicode(), check_lower('hid'), unique=False)
|
||||||
hid.comment = """The Hardware ID (HID) is the ID traceability
|
hid.comment = (
|
||||||
|
"""The Hardware ID (HID) is the ID traceability
|
||||||
systems use to ID a device globally. This field is auto-generated
|
systems use to ID a device globally. This field is auto-generated
|
||||||
from Devicehub using literal identifiers from the device,
|
from Devicehub using literal identifiers from the device,
|
||||||
so it can re-generated *offline*.
|
so it can re-generated *offline*.
|
||||||
""" + HID_CONVERSION_DOC
|
"""
|
||||||
|
+ HID_CONVERSION_DOC
|
||||||
|
)
|
||||||
model = Column(Unicode(), check_lower('model'))
|
model = Column(Unicode(), check_lower('model'))
|
||||||
model.comment = """The model of the device in lower case.
|
model.comment = """The model of the device in lower case.
|
||||||
|
|
||||||
|
@ -118,14 +154,18 @@ class Device(Thing):
|
||||||
image = db.Column(db.URL)
|
image = db.Column(db.URL)
|
||||||
image.comment = "An image of the device."
|
image.comment = "An image of the device."
|
||||||
|
|
||||||
owner_id = db.Column(UUID(as_uuid=True),
|
owner_id = db.Column(
|
||||||
db.ForeignKey(User.id),
|
UUID(as_uuid=True),
|
||||||
nullable=False,
|
db.ForeignKey(User.id),
|
||||||
default=lambda: g.user.id)
|
nullable=False,
|
||||||
|
default=lambda: g.user.id,
|
||||||
|
)
|
||||||
owner = db.relationship(User, primaryjoin=owner_id == User.id)
|
owner = db.relationship(User, primaryjoin=owner_id == User.id)
|
||||||
allocated = db.Column(Boolean, default=False)
|
allocated = db.Column(Boolean, default=False)
|
||||||
allocated.comment = "device is allocated or not."
|
allocated.comment = "device is allocated or not."
|
||||||
devicehub_id = db.Column(db.CIText(), nullable=True, unique=True, default=create_code)
|
devicehub_id = db.Column(
|
||||||
|
db.CIText(), nullable=True, unique=True, default=create_code
|
||||||
|
)
|
||||||
devicehub_id.comment = "device have a unique code."
|
devicehub_id.comment = "device have a unique code."
|
||||||
active = db.Column(Boolean, default=True)
|
active = db.Column(Boolean, default=True)
|
||||||
|
|
||||||
|
@ -152,12 +192,12 @@ class Device(Thing):
|
||||||
'image',
|
'image',
|
||||||
'allocated',
|
'allocated',
|
||||||
'devicehub_id',
|
'devicehub_id',
|
||||||
'active'
|
'active',
|
||||||
}
|
}
|
||||||
|
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
db.Index('device_id', id, postgresql_using='hash'),
|
db.Index('device_id', id, postgresql_using='hash'),
|
||||||
db.Index('type_index', type, postgresql_using='hash')
|
db.Index('type_index', type, postgresql_using='hash'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, **kw) -> None:
|
def __init__(self, **kw) -> None:
|
||||||
|
@ -187,7 +227,9 @@ class Device(Thing):
|
||||||
for ac in actions_one:
|
for ac in actions_one:
|
||||||
ac.real_created = ac.created
|
ac.real_created = ac.created
|
||||||
|
|
||||||
return sorted(chain(actions_multiple, actions_one), key=lambda x: x.real_created)
|
return sorted(
|
||||||
|
chain(actions_multiple, actions_one), key=lambda x: x.real_created
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def problems(self):
|
def problems(self):
|
||||||
|
@ -196,8 +238,9 @@ class Device(Thing):
|
||||||
There can be up to 3 actions: current Snapshot,
|
There can be up to 3 actions: current Snapshot,
|
||||||
current Physical action, current Trading action.
|
current Physical action, current Trading action.
|
||||||
"""
|
"""
|
||||||
from ereuse_devicehub.resources.device import states
|
|
||||||
from ereuse_devicehub.resources.action.models import Snapshot
|
from ereuse_devicehub.resources.action.models import Snapshot
|
||||||
|
from ereuse_devicehub.resources.device import states
|
||||||
|
|
||||||
actions = set()
|
actions = set()
|
||||||
with suppress(LookupError, ValueError):
|
with suppress(LookupError, ValueError):
|
||||||
actions.add(self.last_action_of(Snapshot))
|
actions.add(self.last_action_of(Snapshot))
|
||||||
|
@ -217,11 +260,13 @@ class Device(Thing):
|
||||||
"""
|
"""
|
||||||
# todo ensure to remove materialized values when start using them
|
# todo ensure to remove materialized values when start using them
|
||||||
# todo or self.__table__.columns if inspect fails
|
# todo or self.__table__.columns if inspect fails
|
||||||
return {c.key: getattr(self, c.key, None)
|
return {
|
||||||
for c in inspect(self.__class__).attrs
|
c.key: getattr(self, c.key, None)
|
||||||
if isinstance(c, ColumnProperty)
|
for c in inspect(self.__class__).attrs
|
||||||
and not getattr(c, 'foreign_keys', None)
|
if isinstance(c, ColumnProperty)
|
||||||
and c.key not in self._NON_PHYSICAL_PROPS}
|
and not getattr(c, 'foreign_keys', None)
|
||||||
|
and c.key not in self._NON_PHYSICAL_PROPS
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def public_properties(self) -> Dict[str, object or None]:
|
def public_properties(self) -> Dict[str, object or None]:
|
||||||
|
@ -234,11 +279,13 @@ class Device(Thing):
|
||||||
"""
|
"""
|
||||||
non_public = ['amount', 'transfer_state', 'receiver_id']
|
non_public = ['amount', 'transfer_state', 'receiver_id']
|
||||||
hide_properties = list(self._NON_PHYSICAL_PROPS) + non_public
|
hide_properties = list(self._NON_PHYSICAL_PROPS) + non_public
|
||||||
return {c.key: getattr(self, c.key, None)
|
return {
|
||||||
for c in inspect(self.__class__).attrs
|
c.key: getattr(self, c.key, None)
|
||||||
if isinstance(c, ColumnProperty)
|
for c in inspect(self.__class__).attrs
|
||||||
and not getattr(c, 'foreign_keys', None)
|
if isinstance(c, ColumnProperty)
|
||||||
and c.key not in hide_properties}
|
and not getattr(c, 'foreign_keys', None)
|
||||||
|
and c.key not in hide_properties
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def public_actions(self) -> List[object]:
|
def public_actions(self) -> List[object]:
|
||||||
|
@ -260,6 +307,7 @@ class Device(Thing):
|
||||||
"""The last Rate of the device."""
|
"""The last Rate of the device."""
|
||||||
with suppress(LookupError, ValueError):
|
with suppress(LookupError, ValueError):
|
||||||
from ereuse_devicehub.resources.action.models import Rate
|
from ereuse_devicehub.resources.action.models import Rate
|
||||||
|
|
||||||
return self.last_action_of(Rate)
|
return self.last_action_of(Rate)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -268,12 +316,14 @@ class Device(Thing):
|
||||||
ever been set."""
|
ever been set."""
|
||||||
with suppress(LookupError, ValueError):
|
with suppress(LookupError, ValueError):
|
||||||
from ereuse_devicehub.resources.action.models import Price
|
from ereuse_devicehub.resources.action.models import Price
|
||||||
|
|
||||||
return self.last_action_of(Price)
|
return self.last_action_of(Price)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def last_action_trading(self):
|
def last_action_trading(self):
|
||||||
"""which is the last action trading"""
|
"""which is the last action trading"""
|
||||||
from ereuse_devicehub.resources.device import states
|
from ereuse_devicehub.resources.device import states
|
||||||
|
|
||||||
with suppress(LookupError, ValueError):
|
with suppress(LookupError, ValueError):
|
||||||
return self.last_action_of(*states.Trading.actions())
|
return self.last_action_of(*states.Trading.actions())
|
||||||
|
|
||||||
|
@ -287,6 +337,7 @@ class Device(Thing):
|
||||||
- Management
|
- Management
|
||||||
"""
|
"""
|
||||||
from ereuse_devicehub.resources.device import states
|
from ereuse_devicehub.resources.device import states
|
||||||
|
|
||||||
with suppress(LookupError, ValueError):
|
with suppress(LookupError, ValueError):
|
||||||
return self.last_action_of(*states.Status.actions())
|
return self.last_action_of(*states.Status.actions())
|
||||||
|
|
||||||
|
@ -300,6 +351,7 @@ class Device(Thing):
|
||||||
- Management
|
- Management
|
||||||
"""
|
"""
|
||||||
from ereuse_devicehub.resources.device import states
|
from ereuse_devicehub.resources.device import states
|
||||||
|
|
||||||
status_actions = [ac.t for ac in states.Status.actions()]
|
status_actions = [ac.t for ac in states.Status.actions()]
|
||||||
history = []
|
history = []
|
||||||
for ac in self.actions:
|
for ac in self.actions:
|
||||||
|
@ -329,13 +381,15 @@ class Device(Thing):
|
||||||
if not hasattr(lot, 'trade'):
|
if not hasattr(lot, 'trade'):
|
||||||
return
|
return
|
||||||
|
|
||||||
Status = {0: 'Trade',
|
Status = {
|
||||||
1: 'Confirm',
|
0: 'Trade',
|
||||||
2: 'NeedConfirmation',
|
1: 'Confirm',
|
||||||
3: 'TradeConfirmed',
|
2: 'NeedConfirmation',
|
||||||
4: 'Revoke',
|
3: 'TradeConfirmed',
|
||||||
5: 'NeedConfirmRevoke',
|
4: 'Revoke',
|
||||||
6: 'RevokeConfirmed'}
|
5: 'NeedConfirmRevoke',
|
||||||
|
6: 'RevokeConfirmed',
|
||||||
|
}
|
||||||
|
|
||||||
trade = lot.trade
|
trade = lot.trade
|
||||||
user_from = trade.user_from
|
user_from = trade.user_from
|
||||||
|
@ -408,6 +462,7 @@ class Device(Thing):
|
||||||
"""If the actual trading state is an revoke action, this property show
|
"""If the actual trading state is an revoke action, this property show
|
||||||
the id of that revoke"""
|
the id of that revoke"""
|
||||||
from ereuse_devicehub.resources.device import states
|
from ereuse_devicehub.resources.device import states
|
||||||
|
|
||||||
with suppress(LookupError, ValueError):
|
with suppress(LookupError, ValueError):
|
||||||
action = self.last_action_of(*states.Trading.actions())
|
action = self.last_action_of(*states.Trading.actions())
|
||||||
if action.type == 'Revoke':
|
if action.type == 'Revoke':
|
||||||
|
@ -417,6 +472,7 @@ class Device(Thing):
|
||||||
def physical(self):
|
def physical(self):
|
||||||
"""The actual physical state, None otherwise."""
|
"""The actual physical state, None otherwise."""
|
||||||
from ereuse_devicehub.resources.device import states
|
from ereuse_devicehub.resources.device import states
|
||||||
|
|
||||||
with suppress(LookupError, ValueError):
|
with suppress(LookupError, ValueError):
|
||||||
action = self.last_action_of(*states.Physical.actions())
|
action = self.last_action_of(*states.Physical.actions())
|
||||||
return states.Physical(action.__class__)
|
return states.Physical(action.__class__)
|
||||||
|
@ -425,6 +481,7 @@ class Device(Thing):
|
||||||
def traking(self):
|
def traking(self):
|
||||||
"""The actual traking state, None otherwise."""
|
"""The actual traking state, None otherwise."""
|
||||||
from ereuse_devicehub.resources.device import states
|
from ereuse_devicehub.resources.device import states
|
||||||
|
|
||||||
with suppress(LookupError, ValueError):
|
with suppress(LookupError, ValueError):
|
||||||
action = self.last_action_of(*states.Traking.actions())
|
action = self.last_action_of(*states.Traking.actions())
|
||||||
return states.Traking(action.__class__)
|
return states.Traking(action.__class__)
|
||||||
|
@ -433,6 +490,7 @@ class Device(Thing):
|
||||||
def usage(self):
|
def usage(self):
|
||||||
"""The actual usage state, None otherwise."""
|
"""The actual usage state, None otherwise."""
|
||||||
from ereuse_devicehub.resources.device import states
|
from ereuse_devicehub.resources.device import states
|
||||||
|
|
||||||
with suppress(LookupError, ValueError):
|
with suppress(LookupError, ValueError):
|
||||||
action = self.last_action_of(*states.Usage.actions())
|
action = self.last_action_of(*states.Usage.actions())
|
||||||
return states.Usage(action.__class__)
|
return states.Usage(action.__class__)
|
||||||
|
@ -470,8 +528,11 @@ class Device(Thing):
|
||||||
test has been executed.
|
test has been executed.
|
||||||
"""
|
"""
|
||||||
from ereuse_devicehub.resources.action.models import Test
|
from ereuse_devicehub.resources.action.models import Test
|
||||||
current_tests = unique_everseen((e for e in reversed(self.actions) if isinstance(e, Test)),
|
|
||||||
key=attrgetter('type')) # last test of each type
|
current_tests = unique_everseen(
|
||||||
|
(e for e in reversed(self.actions) if isinstance(e, Test)),
|
||||||
|
key=attrgetter('type'),
|
||||||
|
) # last test of each type
|
||||||
return self._warning_actions(current_tests)
|
return self._warning_actions(current_tests)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -496,7 +557,9 @@ class Device(Thing):
|
||||||
|
|
||||||
def set_hid(self):
|
def set_hid(self):
|
||||||
with suppress(TypeError):
|
with suppress(TypeError):
|
||||||
self.hid = Naming.hid(self.type, self.manufacturer, self.model, self.serial_number)
|
self.hid = Naming.hid(
|
||||||
|
self.type, self.manufacturer, self.model, self.serial_number
|
||||||
|
)
|
||||||
|
|
||||||
def last_action_of(self, *types):
|
def last_action_of(self, *types):
|
||||||
"""Gets the last action of the given types.
|
"""Gets the last action of the given types.
|
||||||
|
@ -509,7 +572,9 @@ class Device(Thing):
|
||||||
actions.sort(key=lambda x: x.created)
|
actions.sort(key=lambda x: x.created)
|
||||||
return next(e for e in reversed(actions) if isinstance(e, types))
|
return next(e for e in reversed(actions) if isinstance(e, types))
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
raise LookupError('{!r} does not contain actions of types {}.'.format(self, types))
|
raise LookupError(
|
||||||
|
'{!r} does not contain actions of types {}.'.format(self, types)
|
||||||
|
)
|
||||||
|
|
||||||
def which_user_put_this_device_in_trace(self):
|
def which_user_put_this_device_in_trace(self):
|
||||||
"""which is the user than put this device in this trade"""
|
"""which is the user than put this device in this trade"""
|
||||||
|
@ -546,6 +611,32 @@ class Device(Thing):
|
||||||
metrics = Metrics(device=self)
|
metrics = Metrics(device=self)
|
||||||
return metrics.get_metrics()
|
return metrics.get_metrics()
|
||||||
|
|
||||||
|
def get_type_logo(self):
|
||||||
|
# This is used for see one logo of type of device in the frontend
|
||||||
|
types = {
|
||||||
|
"Desktop": "bi bi-file-post-fill",
|
||||||
|
"Laptop": "bi bi-laptop",
|
||||||
|
"Server": "bi bi-server",
|
||||||
|
"Processor": "bi bi-cpu",
|
||||||
|
"RamModule": "bi bi-list",
|
||||||
|
"Motherboard": "bi bi-cpu-fill",
|
||||||
|
"NetworkAdapter": "bi bi-hdd-network",
|
||||||
|
"GraphicCard": "bi bi-brush",
|
||||||
|
"SoundCard": "bi bi-volume-up-fill",
|
||||||
|
"Monitor": "bi bi-display",
|
||||||
|
"Display": "bi bi-display",
|
||||||
|
"ComputerMonitor": "bi bi-display",
|
||||||
|
"TelevisionSet": "bi bi-easel",
|
||||||
|
"TV": "bi bi-easel",
|
||||||
|
"Projector": "bi bi-camera-video",
|
||||||
|
"Tablet": "bi bi-tablet-landscape",
|
||||||
|
"Smartphone": "bi bi-phone",
|
||||||
|
"Cellphone": "bi bi-telephone",
|
||||||
|
"HardDrive": "bi bi-hdd-stack",
|
||||||
|
"SolidStateDrive": "bi bi-hdd",
|
||||||
|
}
|
||||||
|
return types.get(self.type, '')
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
return self.id < other.id
|
return self.id < other.id
|
||||||
|
|
||||||
|
@ -571,19 +662,24 @@ class Device(Thing):
|
||||||
|
|
||||||
class DisplayMixin:
|
class DisplayMixin:
|
||||||
"""Base class for the Display Component and the Monitor Device."""
|
"""Base class for the Display Component and the Monitor Device."""
|
||||||
size = Column(Float(decimal_return_scale=1), check_range('size', 2, 150), nullable=True)
|
|
||||||
|
size = Column(
|
||||||
|
Float(decimal_return_scale=1), check_range('size', 2, 150), nullable=True
|
||||||
|
)
|
||||||
size.comment = """The size of the monitor in inches."""
|
size.comment = """The size of the monitor in inches."""
|
||||||
technology = Column(DBEnum(DisplayTech))
|
technology = Column(DBEnum(DisplayTech))
|
||||||
technology.comment = """The technology the monitor uses to display
|
technology.comment = """The technology the monitor uses to display
|
||||||
the image.
|
the image.
|
||||||
"""
|
"""
|
||||||
resolution_width = Column(SmallInteger, check_range('resolution_width', 10, 20000),
|
resolution_width = Column(
|
||||||
nullable=True)
|
SmallInteger, check_range('resolution_width', 10, 20000), nullable=True
|
||||||
|
)
|
||||||
resolution_width.comment = """The maximum horizontal resolution the
|
resolution_width.comment = """The maximum horizontal resolution the
|
||||||
monitor can natively support in pixels.
|
monitor can natively support in pixels.
|
||||||
"""
|
"""
|
||||||
resolution_height = Column(SmallInteger, check_range('resolution_height', 10, 20000),
|
resolution_height = Column(
|
||||||
nullable=True)
|
SmallInteger, check_range('resolution_height', 10, 20000), nullable=True
|
||||||
|
)
|
||||||
resolution_height.comment = """The maximum vertical resolution the
|
resolution_height.comment = """The maximum vertical resolution the
|
||||||
monitor can natively support in pixels.
|
monitor can natively support in pixels.
|
||||||
"""
|
"""
|
||||||
|
@ -622,8 +718,12 @@ class DisplayMixin:
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
if self.size:
|
if self.size:
|
||||||
return '{0.t} {0.serial_number} {0.size}in ({0.aspect_ratio}) {0.technology}'.format(self)
|
return '{0.t} {0.serial_number} {0.size}in ({0.aspect_ratio}) {0.technology}'.format(
|
||||||
return '{0.t} {0.serial_number} 0in ({0.aspect_ratio}) {0.technology}'.format(self)
|
self
|
||||||
|
)
|
||||||
|
return '{0.t} {0.serial_number} 0in ({0.aspect_ratio}) {0.technology}'.format(
|
||||||
|
self
|
||||||
|
)
|
||||||
|
|
||||||
def __format__(self, format_spec: str) -> str:
|
def __format__(self, format_spec: str) -> str:
|
||||||
v = ''
|
v = ''
|
||||||
|
@ -645,6 +745,7 @@ class Computer(Device):
|
||||||
Computer is broadly extended by ``Desktop``, ``Laptop``, and
|
Computer is broadly extended by ``Desktop``, ``Laptop``, and
|
||||||
``Server``. The property ``chassis`` defines it more granularly.
|
``Server``. The property ``chassis`` defines it more granularly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||||
chassis = Column(DBEnum(ComputerChassis), nullable=True)
|
chassis = Column(DBEnum(ComputerChassis), nullable=True)
|
||||||
chassis.comment = """The physical form of the computer.
|
chassis.comment = """The physical form of the computer.
|
||||||
|
@ -652,16 +753,18 @@ class Computer(Device):
|
||||||
It is a subset of the Linux definition of DMI / DMI decode.
|
It is a subset of the Linux definition of DMI / DMI decode.
|
||||||
"""
|
"""
|
||||||
amount = Column(Integer, check_range('amount', min=0, max=100), default=0)
|
amount = Column(Integer, check_range('amount', min=0, max=100), default=0)
|
||||||
owner_id = db.Column(UUID(as_uuid=True),
|
owner_id = db.Column(
|
||||||
db.ForeignKey(User.id),
|
UUID(as_uuid=True),
|
||||||
nullable=False,
|
db.ForeignKey(User.id),
|
||||||
default=lambda: g.user.id)
|
nullable=False,
|
||||||
|
default=lambda: g.user.id,
|
||||||
|
)
|
||||||
# author = db.relationship(User, primaryjoin=owner_id == User.id)
|
# author = db.relationship(User, primaryjoin=owner_id == User.id)
|
||||||
transfer_state = db.Column(IntEnum(TransferState), default=TransferState.Initial, nullable=False)
|
transfer_state = db.Column(
|
||||||
|
IntEnum(TransferState), default=TransferState.Initial, nullable=False
|
||||||
|
)
|
||||||
transfer_state.comment = TransferState.__doc__
|
transfer_state.comment = TransferState.__doc__
|
||||||
receiver_id = db.Column(UUID(as_uuid=True),
|
receiver_id = db.Column(UUID(as_uuid=True), db.ForeignKey(User.id), nullable=True)
|
||||||
db.ForeignKey(User.id),
|
|
||||||
nullable=True)
|
|
||||||
receiver = db.relationship(User, primaryjoin=receiver_id == User.id)
|
receiver = db.relationship(User, primaryjoin=receiver_id == User.id)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
|
@ -684,22 +787,30 @@ class Computer(Device):
|
||||||
@property
|
@property
|
||||||
def ram_size(self) -> int:
|
def ram_size(self) -> int:
|
||||||
"""The total of RAM memory the computer has."""
|
"""The total of RAM memory the computer has."""
|
||||||
return sum(ram.size or 0 for ram in self.components if isinstance(ram, RamModule))
|
return sum(
|
||||||
|
ram.size or 0 for ram in self.components if isinstance(ram, RamModule)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def data_storage_size(self) -> int:
|
def data_storage_size(self) -> int:
|
||||||
"""The total of data storage the computer has."""
|
"""The total of data storage the computer has."""
|
||||||
return sum(ds.size or 0 for ds in self.components if isinstance(ds, DataStorage))
|
return sum(
|
||||||
|
ds.size or 0 for ds in self.components if isinstance(ds, DataStorage)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def processor_model(self) -> str:
|
def processor_model(self) -> str:
|
||||||
"""The model of one of the processors of the computer."""
|
"""The model of one of the processors of the computer."""
|
||||||
return next((p.model for p in self.components if isinstance(p, Processor)), None)
|
return next(
|
||||||
|
(p.model for p in self.components if isinstance(p, Processor)), None
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def graphic_card_model(self) -> str:
|
def graphic_card_model(self) -> str:
|
||||||
"""The model of one of the graphic cards of the computer."""
|
"""The model of one of the graphic cards of the computer."""
|
||||||
return next((p.model for p in self.components if isinstance(p, GraphicCard)), None)
|
return next(
|
||||||
|
(p.model for p in self.components if isinstance(p, GraphicCard)), None
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def network_speeds(self) -> List[int]:
|
def network_speeds(self) -> List[int]:
|
||||||
|
@ -724,16 +835,18 @@ class Computer(Device):
|
||||||
it is not None.
|
it is not None.
|
||||||
"""
|
"""
|
||||||
return set(
|
return set(
|
||||||
privacy for privacy in
|
privacy
|
||||||
(hdd.privacy for hdd in self.components if isinstance(hdd, DataStorage))
|
for privacy in (
|
||||||
|
hdd.privacy for hdd in self.components if isinstance(hdd, DataStorage)
|
||||||
|
)
|
||||||
if privacy
|
if privacy
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def external_document_erasure(self):
|
def external_document_erasure(self):
|
||||||
"""Returns the external ``DataStorage`` proof of erasure.
|
"""Returns the external ``DataStorage`` proof of erasure."""
|
||||||
"""
|
|
||||||
from ereuse_devicehub.resources.action.models import DataWipe
|
from ereuse_devicehub.resources.action.models import DataWipe
|
||||||
|
|
||||||
urls = set()
|
urls = set()
|
||||||
try:
|
try:
|
||||||
ev = self.last_action_of(DataWipe)
|
ev = self.last_action_of(DataWipe)
|
||||||
|
@ -756,8 +869,11 @@ class Computer(Device):
|
||||||
if not self.hid:
|
if not self.hid:
|
||||||
return
|
return
|
||||||
components = self.components if components_snap is None else components_snap
|
components = self.components if components_snap is None else components_snap
|
||||||
macs_network = [c.serial_number for c in components
|
macs_network = [
|
||||||
if c.type == 'NetworkAdapter' and c.serial_number is not None]
|
c.serial_number
|
||||||
|
for c in components
|
||||||
|
if c.type == 'NetworkAdapter' and c.serial_number is not None
|
||||||
|
]
|
||||||
macs_network.sort()
|
macs_network.sort()
|
||||||
mac = macs_network[0] if macs_network else ''
|
mac = macs_network[0] if macs_network else ''
|
||||||
if not mac or mac in self.hid:
|
if not mac or mac in self.hid:
|
||||||
|
@ -823,9 +939,13 @@ class Mobile(Device):
|
||||||
"""
|
"""
|
||||||
ram_size = db.Column(db.Integer, check_range('ram_size', min=128, max=36000))
|
ram_size = db.Column(db.Integer, check_range('ram_size', min=128, max=36000))
|
||||||
ram_size.comment = """The total of RAM of the device in MB."""
|
ram_size.comment = """The total of RAM of the device in MB."""
|
||||||
data_storage_size = db.Column(db.Integer, check_range('data_storage_size', 0, 10 ** 8))
|
data_storage_size = db.Column(
|
||||||
|
db.Integer, check_range('data_storage_size', 0, 10**8)
|
||||||
|
)
|
||||||
data_storage_size.comment = """The total of data storage of the device in MB"""
|
data_storage_size.comment = """The total of data storage of the device in MB"""
|
||||||
display_size = db.Column(db.Float(decimal_return_scale=1), check_range('display_size', min=0.1, max=30.0))
|
display_size = db.Column(
|
||||||
|
db.Float(decimal_return_scale=1), check_range('display_size', min=0.1, max=30.0)
|
||||||
|
)
|
||||||
display_size.comment = """The total size of the device screen"""
|
display_size.comment = """The total size of the device screen"""
|
||||||
|
|
||||||
@validates('imei')
|
@validates('imei')
|
||||||
|
@ -855,21 +975,24 @@ class Cellphone(Mobile):
|
||||||
|
|
||||||
class Component(Device):
|
class Component(Device):
|
||||||
"""A device that can be inside another device."""
|
"""A device that can be inside another device."""
|
||||||
|
|
||||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||||
|
|
||||||
parent_id = Column(BigInteger, ForeignKey(Computer.id))
|
parent_id = Column(BigInteger, ForeignKey(Computer.id))
|
||||||
parent = relationship(Computer,
|
parent = relationship(
|
||||||
backref=backref('components',
|
Computer,
|
||||||
lazy=True,
|
backref=backref(
|
||||||
cascade=CASCADE_DEL,
|
'components',
|
||||||
order_by=lambda: Component.id,
|
lazy=True,
|
||||||
collection_class=OrderedSet),
|
cascade=CASCADE_DEL,
|
||||||
primaryjoin=parent_id == Computer.id)
|
order_by=lambda: Component.id,
|
||||||
|
collection_class=OrderedSet,
|
||||||
__table_args__ = (
|
),
|
||||||
db.Index('parent_index', parent_id, postgresql_using='hash'),
|
primaryjoin=parent_id == Computer.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
__table_args__ = (db.Index('parent_index', parent_id, postgresql_using='hash'),)
|
||||||
|
|
||||||
def similar_one(self, parent: Computer, blacklist: Set[int]) -> 'Component':
|
def similar_one(self, parent: Computer, blacklist: Set[int]) -> 'Component':
|
||||||
"""Gets a component that:
|
"""Gets a component that:
|
||||||
|
|
||||||
|
@ -881,11 +1004,16 @@ class Component(Device):
|
||||||
when looking for similar ones.
|
when looking for similar ones.
|
||||||
"""
|
"""
|
||||||
assert self.hid is None, 'Don\'t use this method with a component that has HID'
|
assert self.hid is None, 'Don\'t use this method with a component that has HID'
|
||||||
component = self.__class__.query \
|
component = (
|
||||||
.filter_by(parent=parent, hid=None, owner_id=self.owner_id,
|
self.__class__.query.filter_by(
|
||||||
**self.physical_properties) \
|
parent=parent,
|
||||||
.filter(~Component.id.in_(blacklist)) \
|
hid=None,
|
||||||
|
owner_id=self.owner_id,
|
||||||
|
**self.physical_properties,
|
||||||
|
)
|
||||||
|
.filter(~Component.id.in_(blacklist))
|
||||||
.first()
|
.first()
|
||||||
|
)
|
||||||
if not component:
|
if not component:
|
||||||
raise ResourceNotFound(self.type)
|
raise ResourceNotFound(self.type)
|
||||||
return component
|
return component
|
||||||
|
@ -908,7 +1036,8 @@ class GraphicCard(JoinedComponentTableMixin, Component):
|
||||||
|
|
||||||
class DataStorage(JoinedComponentTableMixin, Component):
|
class DataStorage(JoinedComponentTableMixin, Component):
|
||||||
"""A device that stores information."""
|
"""A device that stores information."""
|
||||||
size = Column(Integer, check_range('size', min=1, max=10 ** 8))
|
|
||||||
|
size = Column(Integer, check_range('size', min=1, max=10**8))
|
||||||
size.comment = """The size of the data-storage in MB."""
|
size.comment = """The size of the data-storage in MB."""
|
||||||
interface = Column(DBEnum(DataStorageInterface))
|
interface = Column(DBEnum(DataStorageInterface))
|
||||||
|
|
||||||
|
@ -919,6 +1048,7 @@ class DataStorage(JoinedComponentTableMixin, Component):
|
||||||
This is, the last erasure performed to the data storage.
|
This is, the last erasure performed to the data storage.
|
||||||
"""
|
"""
|
||||||
from ereuse_devicehub.resources.action.models import EraseBasic
|
from ereuse_devicehub.resources.action.models import EraseBasic
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ev = self.last_action_of(EraseBasic)
|
ev = self.last_action_of(EraseBasic)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
|
@ -933,9 +1063,9 @@ class DataStorage(JoinedComponentTableMixin, Component):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def external_document_erasure(self):
|
def external_document_erasure(self):
|
||||||
"""Returns the external ``DataStorage`` proof of erasure.
|
"""Returns the external ``DataStorage`` proof of erasure."""
|
||||||
"""
|
|
||||||
from ereuse_devicehub.resources.action.models import DataWipe
|
from ereuse_devicehub.resources.action.models import DataWipe
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ev = self.last_action_of(DataWipe)
|
ev = self.last_action_of(DataWipe)
|
||||||
return ev.document.url.to_text()
|
return ev.document.url.to_text()
|
||||||
|
@ -985,6 +1115,7 @@ class NetworkAdapter(JoinedComponentTableMixin, NetworkMixin, Component):
|
||||||
|
|
||||||
class Processor(JoinedComponentTableMixin, Component):
|
class Processor(JoinedComponentTableMixin, Component):
|
||||||
"""The CPU."""
|
"""The CPU."""
|
||||||
|
|
||||||
speed = Column(Float, check_range('speed', 0.1, 15))
|
speed = Column(Float, check_range('speed', 0.1, 15))
|
||||||
speed.comment = """The regular CPU speed."""
|
speed.comment = """The regular CPU speed."""
|
||||||
cores = Column(SmallInteger, check_range('cores', 1, 10))
|
cores = Column(SmallInteger, check_range('cores', 1, 10))
|
||||||
|
@ -999,6 +1130,7 @@ class Processor(JoinedComponentTableMixin, Component):
|
||||||
|
|
||||||
class RamModule(JoinedComponentTableMixin, Component):
|
class RamModule(JoinedComponentTableMixin, Component):
|
||||||
"""A stick of RAM."""
|
"""A stick of RAM."""
|
||||||
|
|
||||||
size = Column(SmallInteger, check_range('size', min=128, max=17000))
|
size = Column(SmallInteger, check_range('size', min=128, max=17000))
|
||||||
size.comment = """The capacity of the RAM stick."""
|
size.comment = """The capacity of the RAM stick."""
|
||||||
speed = Column(SmallInteger, check_range('speed', min=100, max=10000))
|
speed = Column(SmallInteger, check_range('speed', min=100, max=10000))
|
||||||
|
@ -1016,6 +1148,7 @@ class Display(JoinedComponentTableMixin, DisplayMixin, Component):
|
||||||
mobiles, smart-watches, and so on; excluding ``ComputerMonitor``
|
mobiles, smart-watches, and so on; excluding ``ComputerMonitor``
|
||||||
and ``TelevisionSet``.
|
and ``TelevisionSet``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -1031,14 +1164,16 @@ class Battery(JoinedComponentTableMixin, Component):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def capacity(self) -> float:
|
def capacity(self) -> float:
|
||||||
"""The quantity of """
|
"""The quantity of"""
|
||||||
from ereuse_devicehub.resources.action.models import MeasureBattery
|
from ereuse_devicehub.resources.action.models import MeasureBattery
|
||||||
|
|
||||||
real_size = self.last_action_of(MeasureBattery).size
|
real_size = self.last_action_of(MeasureBattery).size
|
||||||
return real_size / self.size if real_size and self.size else None
|
return real_size / self.size if real_size and self.size else None
|
||||||
|
|
||||||
|
|
||||||
class Camera(Component):
|
class Camera(Component):
|
||||||
"""The camera of a device."""
|
"""The camera of a device."""
|
||||||
|
|
||||||
focal_length = db.Column(db.SmallInteger)
|
focal_length = db.Column(db.SmallInteger)
|
||||||
video_height = db.Column(db.SmallInteger)
|
video_height = db.Column(db.SmallInteger)
|
||||||
video_width = db.Column(db.Integer)
|
video_width = db.Column(db.Integer)
|
||||||
|
@ -1051,6 +1186,7 @@ class Camera(Component):
|
||||||
|
|
||||||
class ComputerAccessory(Device):
|
class ComputerAccessory(Device):
|
||||||
"""Computer peripherals and similar accessories."""
|
"""Computer peripherals and similar accessories."""
|
||||||
|
|
||||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -1073,6 +1209,7 @@ class MemoryCardReader(ComputerAccessory):
|
||||||
|
|
||||||
class Networking(NetworkMixin, Device):
|
class Networking(NetworkMixin, Device):
|
||||||
"""Routers, switches, hubs..."""
|
"""Routers, switches, hubs..."""
|
||||||
|
|
||||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1118,6 +1255,7 @@ class Microphone(Sound):
|
||||||
|
|
||||||
class Video(Device):
|
class Video(Device):
|
||||||
"""Devices related to video treatment."""
|
"""Devices related to video treatment."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -1131,6 +1269,7 @@ class Videoconference(Video):
|
||||||
|
|
||||||
class Cooking(Device):
|
class Cooking(Device):
|
||||||
"""Cooking devices."""
|
"""Cooking devices."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -1182,6 +1321,7 @@ class Manufacturer(db.Model):
|
||||||
Ideally users should use the names from this list when submitting
|
Ideally users should use the names from this list when submitting
|
||||||
devices.
|
devices.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = db.Column(CIText(), primary_key=True)
|
name = db.Column(CIText(), primary_key=True)
|
||||||
name.comment = """The normalized name of the manufacturer."""
|
name.comment = """The normalized name of the manufacturer."""
|
||||||
url = db.Column(URL(), unique=True)
|
url = db.Column(URL(), unique=True)
|
||||||
|
@ -1192,7 +1332,7 @@ class Manufacturer(db.Model):
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
# from https://niallburkley.com/blog/index-columns-for-like-in-postgres/
|
# from https://niallburkley.com/blog/index-columns-for-like-in-postgres/
|
||||||
db.Index('name_index', text('name gin_trgm_ops'), postgresql_using='gin'),
|
db.Index('name_index', text('name gin_trgm_ops'), postgresql_using='gin'),
|
||||||
{'schema': 'common'}
|
{'schema': 'common'},
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -1202,10 +1342,7 @@ class Manufacturer(db.Model):
|
||||||
#: Dialect used to write the CSV
|
#: Dialect used to write the CSV
|
||||||
|
|
||||||
with pathlib.Path(__file__).parent.joinpath('manufacturers.csv').open() as f:
|
with pathlib.Path(__file__).parent.joinpath('manufacturers.csv').open() as f:
|
||||||
cursor.copy_expert(
|
cursor.copy_expert('COPY common.manufacturer FROM STDIN (FORMAT csv)', f)
|
||||||
'COPY common.manufacturer FROM STDIN (FORMAT csv)',
|
|
||||||
f
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
listener_reset_field_updated_in_actual_time(Device)
|
listener_reset_field_updated_in_actual_time(Device)
|
||||||
|
@ -1217,6 +1354,7 @@ def create_code_tag(mapper, connection, device):
|
||||||
this tag is the same of devicehub_id.
|
this tag is the same of devicehub_id.
|
||||||
"""
|
"""
|
||||||
from ereuse_devicehub.resources.tag.model import Tag
|
from ereuse_devicehub.resources.tag.model import Tag
|
||||||
|
|
||||||
if isinstance(device, Computer):
|
if isinstance(device, Computer):
|
||||||
tag = Tag(device_id=device.id, id=device.devicehub_id)
|
tag = Tag(device_id=device.id, id=device.devicehub_id)
|
||||||
db.session.add(tag)
|
db.session.add(tag)
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
STORAGE_KEY = 'tag-spec-key';
|
STORAGE_KEY = 'tag-spec-key';
|
||||||
$("#printerType").on("change", change_size);
|
$("#printerType").on("change", change_size);
|
||||||
|
$(".form-check-input").on("change", change_check);
|
||||||
change_size();
|
change_size();
|
||||||
load_size();
|
load_settings();
|
||||||
|
change_check();
|
||||||
})
|
})
|
||||||
|
|
||||||
function qr_draw(url, id) {
|
function qr_draw(url, id) {
|
||||||
|
@ -16,27 +18,43 @@ function qr_draw(url, id) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function save_size() {
|
function save_settings() {
|
||||||
var height = $("#height-tag").val();
|
var height = $("#height-tag").val();
|
||||||
var width = $("#width-tag").val();
|
var width = $("#width-tag").val();
|
||||||
var sizePreset = $("#printerType").val();
|
var sizePreset = $("#printerType").val();
|
||||||
var data = {"height": height, "width": width, "sizePreset": sizePreset};
|
var data = {"height": height, "width": width, "sizePreset": sizePreset};
|
||||||
|
data['dhid'] = $("#dhidCheck").prop('checked');
|
||||||
|
data['qr'] = $("#qrCheck").prop('checked');
|
||||||
|
data['serial_number'] = $("#serialNumberCheck").prop('checked');
|
||||||
|
data['manufacturer'] = $("#manufacturerCheck").prop('checked');
|
||||||
|
data['model'] = $("#modelCheck").prop('checked');
|
||||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
function load_size() {
|
function load_settings() {
|
||||||
var data = JSON.parse(localStorage.getItem(STORAGE_KEY));
|
var data = JSON.parse(localStorage.getItem(STORAGE_KEY));
|
||||||
if (data){
|
if (data){
|
||||||
$("#height-tag").val(data.height);
|
$("#height-tag").val(data.height);
|
||||||
$("#width-tag").val(data.width);
|
$("#width-tag").val(data.width);
|
||||||
$("#printerType").val(data.sizePreset);
|
$("#printerType").val(data.sizePreset);
|
||||||
|
$("#qrCheck").prop('checked', data.qr);
|
||||||
|
$("#dhidCheck").prop('checked', data.dhid);
|
||||||
|
$("#serialNumberCheck").prop('checked', data.serial_number);
|
||||||
|
$("#manufacturerCheck").prop('checked', data.manufacturer);
|
||||||
|
$("#modelCheck").prop('checked', data.model);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function reset_size() {
|
function reset_settings() {
|
||||||
localStorage.removeItem(STORAGE_KEY);
|
localStorage.removeItem(STORAGE_KEY);
|
||||||
$("#printerType").val('brotherSmall');
|
$("#printerType").val('brotherSmall');
|
||||||
|
$("#qrCheck").prop('checked', true);
|
||||||
|
$("#dhidCheck").prop('checked', true);
|
||||||
|
$("#serialNumberCheck").prop('checked', false);
|
||||||
|
$("#manufacturerCheck").prop('checked', false);
|
||||||
|
$("#modelCheck").prop('checked', false);
|
||||||
change_size();
|
change_size();
|
||||||
|
change_check();
|
||||||
}
|
}
|
||||||
|
|
||||||
function change_size() {
|
function change_size() {
|
||||||
|
@ -50,29 +68,101 @@ function change_size() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function change_check() {
|
||||||
|
if ($("#dhidCheck").prop('checked')) {
|
||||||
|
$(".dhid").show();
|
||||||
|
} else {
|
||||||
|
$(".dhid").hide();
|
||||||
|
}
|
||||||
|
if ($("#serialNumberCheck").prop('checked')) {
|
||||||
|
$(".serial_number").show();
|
||||||
|
} else {
|
||||||
|
$(".serial_number").hide();
|
||||||
|
}
|
||||||
|
if ($("#manufacturerCheck").prop('checked')) {
|
||||||
|
$(".manufacturer").show();
|
||||||
|
} else {
|
||||||
|
$(".manufacturer").hide();
|
||||||
|
}
|
||||||
|
if ($("#modelCheck").prop('checked')) {
|
||||||
|
$(".model").show();
|
||||||
|
} else {
|
||||||
|
$(".model").hide();
|
||||||
|
}
|
||||||
|
if ($("#qrCheck").prop('checked')) {
|
||||||
|
$(".qr").show();
|
||||||
|
} else {
|
||||||
|
$(".qr").hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function printpdf() {
|
function printpdf() {
|
||||||
var border = 2;
|
var border = 2;
|
||||||
|
var line = 5;
|
||||||
var height = parseInt($("#height-tag").val());
|
var height = parseInt($("#height-tag").val());
|
||||||
var width = parseInt($("#width-tag").val());
|
var width = parseInt($("#width-tag").val());
|
||||||
img_side = Math.min(height, width) - 2*border;
|
var img_side = Math.min(height, width) - 2*border;
|
||||||
max_tag_side = (Math.max(height, width)/2) + border;
|
max_tag_side = (Math.max(height, width)/2) + border;
|
||||||
if (max_tag_side < img_side) {
|
if (max_tag_side < img_side) {
|
||||||
max_tag_side = img_side+ 2*border;
|
max_tag_side = img_side + 2*border;
|
||||||
};
|
};
|
||||||
min_tag_side = (Math.min(height, width)/2) + border;
|
min_tag_side = (Math.min(height, width)/2) + border;
|
||||||
var last_tag_code = '';
|
var last_tag_code = '';
|
||||||
|
|
||||||
|
if ($("#serialNumberCheck").prop('checked')) {
|
||||||
|
height += line;
|
||||||
|
};
|
||||||
|
if ($("#manufacturerCheck").prop('checked')) {
|
||||||
|
height += line;
|
||||||
|
};
|
||||||
|
if ($("#modelCheck").prop('checked')) {
|
||||||
|
height += line;
|
||||||
|
};
|
||||||
|
|
||||||
var pdf = new jsPDF('l', 'mm', [width, height]);
|
var pdf = new jsPDF('l', 'mm', [width, height]);
|
||||||
$(".tag").map(function(x, y) {
|
$(".tag").map(function(x, y) {
|
||||||
if (x != 0){
|
if (x != 0){
|
||||||
pdf.addPage();
|
pdf.addPage();
|
||||||
console.log(x)
|
|
||||||
};
|
};
|
||||||
|
var space = line + border;
|
||||||
|
if ($("#qrCheck").prop('checked')) {
|
||||||
|
space += img_side;
|
||||||
|
}
|
||||||
var tag = $(y).text();
|
var tag = $(y).text();
|
||||||
last_tag_code = tag;
|
last_tag_code = tag;
|
||||||
var imgData = $('#'+tag+' img').attr("src");
|
if ($("#qrCheck").prop('checked')) {
|
||||||
pdf.addImage(imgData, 'PNG', border, border, img_side, img_side);
|
var imgData = $('#'+tag+' img').attr("src");
|
||||||
pdf.text(tag, max_tag_side, min_tag_side);
|
pdf.addImage(imgData, 'PNG', border, border, img_side, img_side);
|
||||||
|
};
|
||||||
|
|
||||||
|
if ($("#dhidCheck").prop('checked')) {
|
||||||
|
if ($("#qrCheck").prop('checked')) {
|
||||||
|
pdf.setFontSize(15);
|
||||||
|
pdf.text(tag, max_tag_side, min_tag_side);
|
||||||
|
} else {
|
||||||
|
pdf.setFontSize(15);
|
||||||
|
pdf.text(tag, border, space);
|
||||||
|
space += line;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if ($("#serialNumberCheck").prop('checked')) {
|
||||||
|
var sn = $(y).data('serial-number');
|
||||||
|
pdf.setFontSize(12);
|
||||||
|
pdf.text(sn, border, space);
|
||||||
|
space += line;
|
||||||
|
};
|
||||||
|
if ($("#manufacturerCheck").prop('checked')) {
|
||||||
|
var sn = $(y).data('manufacturer');
|
||||||
|
pdf.setFontSize(12);
|
||||||
|
pdf.text(sn, border, space);
|
||||||
|
space += line;
|
||||||
|
};
|
||||||
|
if ($("#modelCheck").prop('checked')) {
|
||||||
|
var sn = $(y).data('model');
|
||||||
|
pdf.setFontSize(8);
|
||||||
|
pdf.text(sn, border, space);
|
||||||
|
space += line;
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
pdf.save('Tag_'+last_tag_code+'.pdf');
|
pdf.save('Tag_'+last_tag_code+'.pdf');
|
||||||
|
|
|
@ -191,15 +191,6 @@
|
||||||
</ul>
|
</ul>
|
||||||
</li><!-- End Temporal Lots Nav -->
|
</li><!-- End Temporal Lots Nav -->
|
||||||
|
|
||||||
<li class="nav-heading">Utils</li>
|
|
||||||
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link collapsed" href="{{ url_for('labels.label_list')}}">
|
|
||||||
<i class="bi bi-tags"></i>
|
|
||||||
<span>Tags</span>
|
|
||||||
</a>
|
|
||||||
</li><!-- End Tags Page Nav -->
|
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</aside><!-- End Sidebar-->
|
</aside><!-- End Sidebar-->
|
||||||
|
|
|
@ -3,14 +3,14 @@
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
|
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">Adding to a tag</h5>
|
<h5 class="modal-title">Adding to a unique identifier</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form action="{{ url_for('inventory.tag_devices_add') }}" method="post">
|
<form action="{{ url_for('inventory.tag_devices_add') }}" method="post">
|
||||||
{{ form_tag_device.csrf_token }}
|
{{ form_tag_device.csrf_token }}
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
Please write a name of a tag
|
Please write a name of a unique identifier
|
||||||
<select class="form-control selectpicker" id="selectTag" name="tag" data-live-search="true">
|
<select class="form-control selectpicker" id="selectTag" name="tag" data-live-search="true">
|
||||||
{% for tag in tags %}
|
{% for tag in tags %}
|
||||||
<option value="{{ tag.id }}">{{ tag.id }}</option>
|
<option value="{{ tag.id }}">{{ tag.id }}</option>
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
</select>
|
</select>
|
||||||
<input class="devicesList" type="hidden" name="device" />
|
<input class="devicesList" type="hidden" name="device" />
|
||||||
<p class="text-danger pol">
|
<p class="text-danger pol">
|
||||||
You need select first one device and only one for add this in a tag
|
You need select first one device and only one for add this in a unique identifier
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -217,25 +217,42 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="btn-group dropdown m-1" uib-dropdown="">
|
<div class="btn-group dropdown m-1" uib-dropdown="">
|
||||||
<button id="btnTags" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
<button id="btnUniqueID" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<i class="bi bi-tag"></i>
|
<i class="bi bi-tag"></i>
|
||||||
Tags
|
Unique Identifiers
|
||||||
</button>
|
</button>
|
||||||
<span class="d-none" id="unlinkTagAlertModal" data-bs-toggle="modal" data-bs-target="#unlinkTagErrorModal"></span>
|
<span class="d-none" id="unlinkTagAlertModal" data-bs-toggle="modal" data-bs-target="#unlinkTagErrorModal"></span>
|
||||||
<span class="d-none" id="addTagAlertModal" data-bs-toggle="modal" data-bs-target="#addingTagModal"></span>
|
<span class="d-none" id="addTagAlertModal" data-bs-toggle="modal" data-bs-target="#addingTagModal"></span>
|
||||||
<ul class="dropdown-menu" aria-labelledby="btnTags">
|
<ul class="dropdown-menu" aria-labelledby="btnUniqueID">
|
||||||
<li>
|
<li>
|
||||||
<a href="javascript:addTag()" class="dropdown-item">
|
<a href="javascript:addTag()" class="dropdown-item">
|
||||||
<i class="bi bi-plus"></i>
|
<i class="bi bi-plus"></i>
|
||||||
Add Tag to selected Device
|
Add Unique Identifier to selected Device
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="javascript:removeTag()" class="dropdown-item">
|
<a href="javascript:removeTag()" class="dropdown-item">
|
||||||
<i class="bi bi-x"></i>
|
<i class="bi bi-x"></i>
|
||||||
Remove Tag from selected Device
|
Remove Unique Identifier from selected Device
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item" href="{{ url_for('labels.label_list')}}">
|
||||||
|
<i class="bi bi-tools"></i>
|
||||||
|
Unique Identifier Management
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btn-group dropdown m-1" uib-dropdown="">
|
||||||
|
<button id="btnTags" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="bi bi-tag"></i>
|
||||||
|
Labels
|
||||||
|
</button>
|
||||||
|
<span class="d-none" id="unlinkTagAlertModal" data-bs-toggle="modal" data-bs-target="#unlinkTagErrorModal"></span>
|
||||||
|
<span class="d-none" id="addTagAlertModal" data-bs-toggle="modal" data-bs-target="#addingTagModal"></span>
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="btnTags">
|
||||||
<li>
|
<li>
|
||||||
<form id="print_labels" method="post" action="{{ url_for('labels.print_labels') }}">
|
<form id="print_labels" method="post" action="{{ url_for('labels.print_labels') }}">
|
||||||
{% for f in form_print_labels %}
|
{% for f in form_print_labels %}
|
||||||
|
@ -318,7 +335,7 @@
|
||||||
<th scope="col">Select</th>
|
<th scope="col">Select</th>
|
||||||
<th scope="col">Title</th>
|
<th scope="col">Title</th>
|
||||||
<th scope="col">DHID</th>
|
<th scope="col">DHID</th>
|
||||||
<th scope="col">Tags</th>
|
<th scope="col">Unique Identifiers</th>
|
||||||
<th scope="col">Status</th>
|
<th scope="col">Status</th>
|
||||||
<th scope="col" data-type="date" data-format="DD-MM-YYYY">Update</th>
|
<th scope="col" data-type="date" data-format="DD-MM-YYYY">Update</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -336,6 +353,9 @@
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
{% if dev.get_type_logo() %}
|
||||||
|
<i class="{{ dev.get_type_logo() }}" title="{{ dev.type }}"></i>
|
||||||
|
{% endif %}
|
||||||
<a href="{{ url_for('inventory.device_details', id=dev.devicehub_id)}}">
|
<a href="{{ url_for('inventory.device_details', id=dev.devicehub_id)}}">
|
||||||
{{ dev.verbose_name }}
|
{{ dev.verbose_name }}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
<div class="pt-4 pb-2">
|
<div class="pt-4 pb-2">
|
||||||
<h1 class="card-title text-center pb-0 fs-4">Unlink Tag from Device</h1>
|
<h1 class="card-title text-center pb-0 fs-4">Unlink Unique Identifier from Device</h1>
|
||||||
<p class="text-center small">Please enter a code for the tag.</p>
|
<p class="text-center small">Please enter a code for the unique identifier.</p>
|
||||||
{% if form.form_errors %}
|
{% if form.form_errors %}
|
||||||
<p class="text-danger">
|
<p class="text-danger">
|
||||||
{% for error in form.form_errors %}
|
{% for error in form.form_errors %}
|
||||||
|
@ -33,10 +33,10 @@
|
||||||
{{ form.csrf_token }}
|
{{ form.csrf_token }}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="tag" class="form-label">Tag</label>
|
<label for="tag" class="form-label">Unique Identifier</label>
|
||||||
<div class="input-group has-validation">
|
<div class="input-group has-validation">
|
||||||
{{ form.tag(class_="form-control") }}
|
{{ form.tag(class_="form-control") }}
|
||||||
<div class="invalid-feedback">Please select tag.</div>
|
<div class="invalid-feedback">Please select unique identifier.</div>
|
||||||
</div>
|
</div>
|
||||||
{% if form.tag.errors %}
|
{% if form.tag.errors %}
|
||||||
<p class="text-danger">
|
<p class="text-danger">
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
<h1>Inventory</h1>
|
<h1>Inventory</h1>
|
||||||
<nav>
|
<nav>
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item"><a href="{{ url_for('labels.label_list')}}">Tag management</a></li>
|
<li class="breadcrumb-item"><a href="{{ url_for('labels.label_list')}}">Unique Identifier management</a></li>
|
||||||
<li class="breadcrumb-item active">Tag details {{ tag.id }}</li>
|
<li class="breadcrumb-item active">Unique Identifier details {{ tag.id }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
</div><!-- End Page Title -->
|
</div><!-- End Page Title -->
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-3 col-md-4 label ">Type</div>
|
<div class="col-lg-3 col-md-4 label ">Type</div>
|
||||||
<div class="col-lg-9 col-md-8">{% if tag.provider %}UnNamed Tag{% else %}Named{% endif %}</div>
|
<div class="col-lg-9 col-md-8">{% if tag.provider %}UnNamed Unique Identifier{% else %}Named Unique Identifier{% endif %}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -43,16 +43,49 @@
|
||||||
<h5 class="card-title">Print Label</h5>
|
<h5 class="card-title">Print Label</h5>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-3 col-md-4">
|
<div class="col-lg-3 col-md-4">
|
||||||
<div style="width:256px; height:148px; border: solid 1px; padding: 10px;">
|
<div style="width:256px; min-height:148px; border: solid 1px; padding: 10px;">
|
||||||
<div id="print">
|
<div id="print">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col qr">
|
||||||
<div id="{{ tag.id }}"></div>
|
<div id="{{ tag.id }}"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col dhid">
|
||||||
<div style="padding-top: 55px"><b class="tag">{{ tag.id }}</b></div>
|
<div style="padding-top: 55px">
|
||||||
|
{% if tag.device %}
|
||||||
|
<b class="tag" data-serial-number="{{ tag.device.serial_number or '' }}"
|
||||||
|
data-manufacturer="{{ tag.device.manufacturer or '' }}"
|
||||||
|
data-model="{{ tag.device.model or '' }}">{{ tag.id }}</b>
|
||||||
|
{% else %}
|
||||||
|
<b class="tag" data-serial-number=""
|
||||||
|
data-manufacturer=""
|
||||||
|
data-model="">{{ tag.id }}</b>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if tag.device %}
|
||||||
|
<div class="row serial_number" style="display: none">
|
||||||
|
<div class="col">
|
||||||
|
<div>
|
||||||
|
<b>{{ tag.device.serial_number or '' }}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row manufacturer" style="display: none">
|
||||||
|
<div class="col">
|
||||||
|
<div>
|
||||||
|
<b>{{ tag.device.manufacturer or '' }}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row model" style="display: none">
|
||||||
|
<div class="col">
|
||||||
|
<div>
|
||||||
|
<span style="font-size: 12px;">{{ tag.device.model or '' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -84,20 +117,43 @@
|
||||||
<span class="input-group-text">mm</span>
|
<span class="input-group-text">mm</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if tag.device %}
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="form-switch">
|
||||||
|
<input class="form-check-input" name="qr" type="checkbox" id="qrCheck" checked="">
|
||||||
|
<label class="form-check-label" for="qrCheck">QR</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-switch">
|
||||||
|
<input class="form-check-input" name="dhid" type="checkbox" id="dhidCheck" checked="">
|
||||||
|
<label class="form-check-label" for="dhidCheck">Unique Identifier</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-switch">
|
||||||
|
<input class="form-check-input" name="serial_number" type="checkbox" id="serialNumberCheck">
|
||||||
|
<label class="form-check-label" for="serialNumberCheck">Serial number</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-switch">
|
||||||
|
<input class="form-check-input" name="manufacturer" type="checkbox" id="manufacturerCheck">
|
||||||
|
<label class="form-check-label" for="manufacturerCheck">Manufacturer</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-switch">
|
||||||
|
<input class="form-check-input" name="model" type="checkbox" id="modelCheck">
|
||||||
|
<label class="form-check-label" for="modelCheck">Model</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="row mt-5">
|
||||||
|
<div class="col-lg-3 col-md-4">
|
||||||
|
<a href="javascript:printpdf()" class="btn btn-success">Print labels</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-3 col-md-4">
|
||||||
|
<a href="javascript:save_settings()" class="btn btn-primary">Save settings</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-3 col-md-4">
|
||||||
|
<a href="javascript:reset_settings()" class="btn btn-danger">Reset settings</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-3 col-md-4">
|
|
||||||
<a href="javascript:printpdf()" class="btn btn-success">Print</a>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-3 col-md-4">
|
|
||||||
<a href="javascript:save_size()" class="btn btn-primary">Save</a>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-3 col-md-4">
|
|
||||||
<a href="javascript:reset_size()" class="btn btn-danger">Reset</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
<div class="btn-group dropdown m-1">
|
<div class="btn-group dropdown m-1">
|
||||||
<a href="{{ url_for('labels.tag_add')}}" type="button" class="btn btn-primary">
|
<a href="{{ url_for('labels.tag_add')}}" type="button" class="btn btn-primary">
|
||||||
<i class="bi bi-plus"></i>
|
<i class="bi bi-plus"></i>
|
||||||
Create Named Tag
|
Create Named Unique Identifier
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
<div class="btn-group dropdown m-1" uib-dropdown="">
|
<div class="btn-group dropdown m-1" uib-dropdown="">
|
||||||
<a href="{{ url_for('labels.tag_unnamed_add')}}" type="button" class="btn btn-primary">
|
<a href="{{ url_for('labels.tag_unnamed_add')}}" type="button" class="btn btn-primary">
|
||||||
<i class="bi bi-plus"></i>
|
<i class="bi bi-plus"></i>
|
||||||
Create UnNamed Tag
|
Create UnNamed Unique Identifier
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
{% for tag in tags %}
|
{% for tag in tags %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{{ url_for('labels.label_details', id=tag.id) }}">{{ tag.id }}</a></td>
|
<td><a href="{{ url_for('labels.label_details', id=tag.id) }}">{{ tag.id }}</a></td>
|
||||||
<td>{% if tag.provider %}Unnamed tag {% else %}Named tag{% endif %}</td>
|
<td>{% if tag.provider %}Unnamed unique Identifier {% else %}Named unique identifier{% endif %}</td>
|
||||||
<td>{{ tag.get_provider }}</td>
|
<td>{{ tag.get_provider }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if tag.device %}
|
{% if tag.device %}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<h1>Print Labels</h1>
|
<h1>Print Labels</h1>
|
||||||
<nav>
|
<nav>
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item"><a href="{{ url_for('labels.label_list')}}">Tag management</a></li>
|
<li class="breadcrumb-item"><a href="{{ url_for('labels.label_list')}}">Unique Identifier management</a></li>
|
||||||
<li class="breadcrumb-item active">Print Labels</li>
|
<li class="breadcrumb-item active">Print Labels</li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -24,16 +24,39 @@
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-3 col-md-4">
|
<div class="col-lg-3 col-md-4">
|
||||||
{% for tag in tags %}
|
{% for dev in devices %}
|
||||||
<div style="width:256px; height:148px; border: solid 1px; padding: 10px;">
|
<div style="width:256px; min-height:148px; border: solid 1px; padding: 10px;">
|
||||||
<div id="print">
|
<div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col qr">
|
||||||
<div id="{{ tag.id }}"></div>
|
<div id="{{ dev.devicehub_id }}"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col dhid">
|
||||||
<div style="padding-top: 55px">
|
<div style="padding-top: 55px">
|
||||||
<b class="tag">{{ tag.id }}</b>
|
<b class="tag" data-serial-number="{{ dev.serial_number or '' }}"
|
||||||
|
data-manufacturer="{{ dev.manufacturer or '' }}"
|
||||||
|
data-model="{{ dev.model or '' }}">{{ dev.devicehub_id }}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row serial_number" style="display: none">
|
||||||
|
<div class="col">
|
||||||
|
<div>
|
||||||
|
<b>{{ dev.serial_number or '' }}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row manufacturer" style="display: none">
|
||||||
|
<div class="col">
|
||||||
|
<div>
|
||||||
|
<b>{{ dev.manufacturer or '' }}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row model" style="display: none">
|
||||||
|
<div class="col">
|
||||||
|
<div>
|
||||||
|
<span style="font-size: 12px;">{{ dev.model or '' }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -71,20 +94,41 @@
|
||||||
<span class="input-group-text">mm</span>
|
<span class="input-group-text">mm</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="form-switch">
|
||||||
|
<input class="form-check-input" name="qr" type="checkbox" id="qrCheck" checked="">
|
||||||
|
<label class="form-check-label" for="qrCheck">QR</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-switch">
|
||||||
|
<input class="form-check-input" name="dhid" type="checkbox" id="dhidCheck" checked="">
|
||||||
|
<label class="form-check-label" for="dhidCheck">Dhid</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-switch">
|
||||||
|
<input class="form-check-input" name="serial_number" type="checkbox" id="serialNumberCheck">
|
||||||
|
<label class="form-check-label" for="serialNumberCheck">Serial number</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-switch">
|
||||||
|
<input class="form-check-input" name="manufacturer" type="checkbox" id="manufacturerCheck">
|
||||||
|
<label class="form-check-label" for="manufacturerCheck">Manufacturer</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-switch">
|
||||||
|
<input class="form-check-input" name="model" type="checkbox" id="modelCheck">
|
||||||
|
<label class="form-check-label" for="modelCheck">Model</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-5">
|
||||||
|
<div class="col-lg-3 col-md-4">
|
||||||
|
<a href="javascript:printpdf()" class="btn btn-success">Print labels</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-3 col-md-4">
|
||||||
|
<a href="javascript:save_settings()" class="btn btn-primary">Save settings</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-3 col-md-4">
|
||||||
|
<a href="javascript:reset_settings()" class="btn btn-danger">Reset settings</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-5">
|
|
||||||
<div class="col-lg-3 col-md-4">
|
|
||||||
<a href="javascript:printpdf()" class="btn btn-success">Print</a>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-3 col-md-4">
|
|
||||||
<a href="javascript:save_size()" class="btn btn-primary">Save</a>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-3 col-md-4">
|
|
||||||
<a href="javascript:reset_size()" class="btn btn-danger">Reset</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -96,8 +140,8 @@
|
||||||
<script src="{{ url_for('static', filename='js/jspdf.min.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/jspdf.min.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/print.pdf.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/print.pdf.js') }}"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
{% for tag in tags %}
|
{% for dev in devices %}
|
||||||
qr_draw("{{ url_for('inventory.device_details', id=tag.device.devicehub_id, _external=True) }}", "#{{ tag.id }}")
|
qr_draw("{{ url_for('inventory.device_details', id=dev.devicehub_id, _external=True) }}", "#{{ dev.devicehub_id }}")
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</script>
|
</script>
|
||||||
{% endblock main %}
|
{% endblock main %}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<h1>{{ title }}</h1>
|
<h1>{{ title }}</h1>
|
||||||
<nav>
|
<nav>
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item"><a href="{{ url_for('labels.label_list')}}">Tag management</a></li>
|
<li class="breadcrumb-item"><a href="{{ url_for('labels.label_list')}}">Unique Identifier management</a></li>
|
||||||
<li class="breadcrumb-item">{{ page_title }}</li>
|
<li class="breadcrumb-item">{{ page_title }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -19,8 +19,8 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
<div class="pt-4 pb-2">
|
<div class="pt-4 pb-2">
|
||||||
<h5 class="card-title text-center pb-0 fs-4">Add a new Tag</h5>
|
<h5 class="card-title text-center pb-0 fs-4">Add a new Unique Identifier</h5>
|
||||||
<p class="text-center small">Please enter a code for the tag.</p>
|
<p class="text-center small">Please enter a code for the unique identifier.</p>
|
||||||
{% if form.form_errors %}
|
{% if form.form_errors %}
|
||||||
<p class="text-danger">
|
<p class="text-danger">
|
||||||
{% for error in form.form_errors %}
|
{% for error in form.form_errors %}
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
<label for="code" class="form-label">code</label>
|
<label for="code" class="form-label">code</label>
|
||||||
<div class="input-group has-validation">
|
<div class="input-group has-validation">
|
||||||
<input type="text" name="code" class="form-control" required value="{{ form.code.data|default('', true) }}">
|
<input type="text" name="code" class="form-control" required value="{{ form.code.data|default('', true) }}">
|
||||||
<div class="invalid-feedback">Please enter a code of the tag.</div>
|
<div class="invalid-feedback">Please enter a code of the unique identifier.</div>
|
||||||
</div>
|
</div>
|
||||||
{% if form.code.errors %}
|
{% if form.code.errors %}
|
||||||
<p class="text-danger">
|
<p class="text-danger">
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<h1>{{ title }}</h1>
|
<h1>{{ title }}</h1>
|
||||||
<nav>
|
<nav>
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item"><a href="{{ url_for('labels.label_list')}}">Tag management</a></li>
|
<li class="breadcrumb-item"><a href="{{ url_for('labels.label_list')}}">Unique Identifier management</a></li>
|
||||||
<li class="breadcrumb-item">{{ page_title }}</li>
|
<li class="breadcrumb-item">{{ page_title }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -19,8 +19,8 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
<div class="pt-4 pb-2">
|
<div class="pt-4 pb-2">
|
||||||
<h5 class="card-title text-center pb-0 fs-4">Add new Unnamed Tags</h5>
|
<h5 class="card-title text-center pb-0 fs-4">Add new Unnamed Unique Identifiers</h5>
|
||||||
<p class="text-center small">Please enter a number of the tags to issue.</p>
|
<p class="text-center small">Please enter a number of the unique identifiers to issue.</p>
|
||||||
{% if form.form_errors %}
|
{% if form.form_errors %}
|
||||||
<p class="text-danger">
|
<p class="text-danger">
|
||||||
{% for error in form.form_errors %}
|
{% for error in form.form_errors %}
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
<label for="code" class="form-label">Amount</label>
|
<label for="code" class="form-label">Amount</label>
|
||||||
<div class="input-group has-validation">
|
<div class="input-group has-validation">
|
||||||
{{ form.amount(class_="form-control") }}
|
{{ form.amount(class_="form-control") }}
|
||||||
<div class="invalid-feedback">Please enter a number of the tags to issue.</div>
|
<div class="invalid-feedback">Please enter a number of the unique identifiers to issue.</div>
|
||||||
</div>
|
</div>
|
||||||
{% if form.amount.errors %}
|
{% if form.amount.errors %}
|
||||||
<p class="text-danger">
|
<p class="text-danger">
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import flask
|
import flask
|
||||||
from flask import Blueprint
|
from flask import Blueprint, g
|
||||||
from flask.views import View
|
from flask.views import View
|
||||||
from flask_login import current_user, login_required, login_user, logout_user
|
from flask_login import current_user, login_required, login_user, logout_user
|
||||||
|
from sqlalchemy import or_
|
||||||
|
|
||||||
from ereuse_devicehub import __version__, messages
|
from ereuse_devicehub import __version__, messages
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.forms import LoginForm, PasswordForm
|
from ereuse_devicehub.forms import LoginForm, PasswordForm
|
||||||
|
from ereuse_devicehub.resources.action.models import Trade
|
||||||
|
from ereuse_devicehub.resources.lot.models import Lot
|
||||||
from ereuse_devicehub.resources.user.models import User
|
from ereuse_devicehub.resources.user.models import User
|
||||||
from ereuse_devicehub.utils import is_safe_url
|
from ereuse_devicehub.utils import is_safe_url
|
||||||
|
|
||||||
|
@ -46,18 +49,45 @@ class LogoutView(View):
|
||||||
return flask.redirect(flask.url_for('core.login'))
|
return flask.redirect(flask.url_for('core.login'))
|
||||||
|
|
||||||
|
|
||||||
class UserProfileView(View):
|
class GenericMixView(View):
|
||||||
|
decorators = [login_required]
|
||||||
|
|
||||||
|
def get_lots(self):
|
||||||
|
return (
|
||||||
|
Lot.query.outerjoin(Trade)
|
||||||
|
.filter(
|
||||||
|
or_(
|
||||||
|
Trade.user_from == g.user,
|
||||||
|
Trade.user_to == g.user,
|
||||||
|
Lot.owner_id == g.user.id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_context(self):
|
||||||
|
self.context = {
|
||||||
|
'lots': self.get_lots(),
|
||||||
|
'version': __version__,
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.context
|
||||||
|
|
||||||
|
|
||||||
|
class UserProfileView(GenericMixView):
|
||||||
decorators = [login_required]
|
decorators = [login_required]
|
||||||
template_name = 'ereuse_devicehub/user_profile.html'
|
template_name = 'ereuse_devicehub/user_profile.html'
|
||||||
|
|
||||||
def dispatch_request(self):
|
def dispatch_request(self):
|
||||||
context = {
|
self.get_context()
|
||||||
'current_user': current_user,
|
self.context.update(
|
||||||
'version': __version__,
|
{
|
||||||
'password_form': PasswordForm(),
|
'current_user': current_user,
|
||||||
}
|
'password_form': PasswordForm(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return flask.render_template(self.template_name, **context)
|
return flask.render_template(self.template_name, **self.context)
|
||||||
|
|
||||||
|
|
||||||
class UserPasswordView(View):
|
class UserPasswordView(View):
|
||||||
|
|
|
@ -1,27 +1,29 @@
|
||||||
import io
|
import io
|
||||||
import uuid
|
import uuid
|
||||||
import jwt
|
|
||||||
import ereuse_utils
|
|
||||||
from contextlib import redirect_stdout
|
from contextlib import redirect_stdout
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from decouple import config
|
|
||||||
|
|
||||||
import boltons.urlutils
|
import boltons.urlutils
|
||||||
|
import ereuse_utils
|
||||||
|
import jwt
|
||||||
import pytest
|
import pytest
|
||||||
import yaml
|
import yaml
|
||||||
|
from decouple import config
|
||||||
from psycopg2 import IntegrityError
|
from psycopg2 import IntegrityError
|
||||||
from sqlalchemy.exc import ProgrammingError
|
from sqlalchemy.exc import ProgrammingError
|
||||||
|
|
||||||
from ereuse_devicehub.client import Client, UserClient
|
from ereuse_devicehub.client import Client, UserClient, UserClientFlask
|
||||||
from ereuse_devicehub.config import DevicehubConfig
|
from ereuse_devicehub.config import DevicehubConfig
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.devicehub import Devicehub
|
from ereuse_devicehub.devicehub import Devicehub
|
||||||
|
from ereuse_devicehub.inventory.views import devices
|
||||||
|
from ereuse_devicehub.labels.views import labels
|
||||||
from ereuse_devicehub.resources.agent.models import Person
|
from ereuse_devicehub.resources.agent.models import Person
|
||||||
from ereuse_devicehub.resources.tag import Tag
|
|
||||||
from ereuse_devicehub.resources.user.models import User
|
|
||||||
from ereuse_devicehub.resources.user.models import Session
|
|
||||||
from ereuse_devicehub.resources.enums import SessionType
|
from ereuse_devicehub.resources.enums import SessionType
|
||||||
|
from ereuse_devicehub.resources.tag import Tag
|
||||||
|
from ereuse_devicehub.resources.user.models import Session, User
|
||||||
|
from ereuse_devicehub.views import core
|
||||||
|
|
||||||
STARTT = datetime(year=2000, month=1, day=1, hour=1)
|
STARTT = datetime(year=2000, month=1, day=1, hour=1)
|
||||||
"""A dummy starting time to use in tests."""
|
"""A dummy starting time to use in tests."""
|
||||||
|
@ -50,6 +52,20 @@ def config():
|
||||||
|
|
||||||
@pytest.fixture(scope='session')
|
@pytest.fixture(scope='session')
|
||||||
def _app(config: TestConfig) -> Devicehub:
|
def _app(config: TestConfig) -> Devicehub:
|
||||||
|
# dh_config = DevicehubConfig()
|
||||||
|
# config = TestConfig(dh_config)
|
||||||
|
app = Devicehub(inventory='test', config=config, db=db)
|
||||||
|
app.register_blueprint(core)
|
||||||
|
app.register_blueprint(devices)
|
||||||
|
app.register_blueprint(labels)
|
||||||
|
app.config["SQLALCHEMY_RECORD_QUERIES"] = True
|
||||||
|
app.config['PROFILE'] = True
|
||||||
|
# app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[30])
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def _app2(config: TestConfig) -> Devicehub:
|
||||||
return Devicehub(inventory='test', config=config, db=db)
|
return Devicehub(inventory='test', config=config, db=db)
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,13 +77,15 @@ def app(request, _app: Devicehub) -> Devicehub:
|
||||||
db.drop_all()
|
db.drop_all()
|
||||||
|
|
||||||
def _init():
|
def _init():
|
||||||
_app.init_db(name='Test Inventory',
|
_app.init_db(
|
||||||
org_name='FooOrg',
|
name='Test Inventory',
|
||||||
org_id='foo-org-id',
|
org_name='FooOrg',
|
||||||
tag_url=boltons.urlutils.URL('https://example.com'),
|
org_id='foo-org-id',
|
||||||
tag_token=uuid.UUID('52dacef0-6bcb-4919-bfed-f10d2c96ecee'),
|
tag_url=boltons.urlutils.URL('https://example.com'),
|
||||||
erase=False,
|
tag_token=uuid.UUID('52dacef0-6bcb-4919-bfed-f10d2c96ecee'),
|
||||||
common=True)
|
erase=False,
|
||||||
|
common=True,
|
||||||
|
)
|
||||||
|
|
||||||
with _app.app_context():
|
with _app.app_context():
|
||||||
try:
|
try:
|
||||||
|
@ -99,7 +117,9 @@ def user(app: Devicehub) -> UserClient:
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
password = 'foo'
|
password = 'foo'
|
||||||
user = create_user(password=password)
|
user = create_user(password=password)
|
||||||
client = UserClient(app, user.email, password, response_wrapper=app.response_class)
|
client = UserClient(
|
||||||
|
app, user.email, password, response_wrapper=app.response_class
|
||||||
|
)
|
||||||
client.login()
|
client.login()
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
@ -111,11 +131,34 @@ def user2(app: Devicehub) -> UserClient:
|
||||||
password = 'foo'
|
password = 'foo'
|
||||||
email = 'foo2@foo.com'
|
email = 'foo2@foo.com'
|
||||||
user = create_user(email=email, password=password)
|
user = create_user(email=email, password=password)
|
||||||
client = UserClient(app, user.email, password, response_wrapper=app.response_class)
|
client = UserClient(
|
||||||
|
app, user.email, password, response_wrapper=app.response_class
|
||||||
|
)
|
||||||
client.login()
|
client.login()
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def user3(app: Devicehub) -> UserClientFlask:
|
||||||
|
"""Gets a client with a logged-in dummy user."""
|
||||||
|
with app.app_context():
|
||||||
|
password = 'foo'
|
||||||
|
user = create_user(password=password)
|
||||||
|
client = UserClientFlask(app, user.email, password)
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def user4(app: Devicehub) -> UserClient:
|
||||||
|
"""Gets a client with a logged-in dummy user."""
|
||||||
|
with app.app_context():
|
||||||
|
password = 'foo'
|
||||||
|
email = 'foo2@foo.com'
|
||||||
|
user = create_user(email=email, password=password)
|
||||||
|
client = UserClientFlask(app, user.email, password)
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
def create_user(email='foo@foo.com', password='foo') -> User:
|
def create_user(email='foo@foo.com', password='foo') -> User:
|
||||||
user = User(email=email, password=password)
|
user = User(email=email, password=password)
|
||||||
user.individuals.add(Person(name='Timmy'))
|
user.individuals.add(Person(name='Timmy'))
|
||||||
|
@ -145,16 +188,13 @@ def auth_app_context(app: Devicehub):
|
||||||
def json_encode(dev: str) -> dict:
|
def json_encode(dev: str) -> dict:
|
||||||
"""Encode json."""
|
"""Encode json."""
|
||||||
data = {"type": "Snapshot"}
|
data = {"type": "Snapshot"}
|
||||||
data['data'] = jwt.encode(dev,
|
data['data'] = jwt.encode(
|
||||||
P,
|
dev, P, algorithm="HS256", json_encoder=ereuse_utils.JSONEncoder
|
||||||
algorithm="HS256",
|
|
||||||
json_encoder=ereuse_utils.JSONEncoder
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def yaml2json(name: str) -> dict:
|
def yaml2json(name: str) -> dict:
|
||||||
"""Opens and parses a YAML file from the ``files`` subdir."""
|
"""Opens and parses a YAML file from the ``files`` subdir."""
|
||||||
with Path(__file__).parent.joinpath('files').joinpath(name + '.yaml').open() as f:
|
with Path(__file__).parent.joinpath('files').joinpath(name + '.yaml').open() as f:
|
||||||
|
@ -168,7 +208,9 @@ def file(name: str) -> dict:
|
||||||
|
|
||||||
def file_workbench(name: str) -> dict:
|
def file_workbench(name: str) -> dict:
|
||||||
"""Opens and parses a YAML file from the ``files`` subdir."""
|
"""Opens and parses a YAML file from the ``files`` subdir."""
|
||||||
with Path(__file__).parent.joinpath('workbench_files').joinpath(name + '.json').open() as f:
|
with Path(__file__).parent.joinpath('workbench_files').joinpath(
|
||||||
|
name + '.json'
|
||||||
|
).open() as f:
|
||||||
return yaml.load(f)
|
return yaml.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,7 +1,7 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ereuse_devicehub.devicehub import Devicehub
|
|
||||||
from ereuse_devicehub.client import Client
|
from ereuse_devicehub.client import Client
|
||||||
|
from ereuse_devicehub.devicehub import Devicehub
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.mvp
|
@pytest.mark.mvp
|
||||||
|
@ -28,37 +28,65 @@ def test_api_docs(client: Client):
|
||||||
"""Tests /apidocs correct initialization."""
|
"""Tests /apidocs correct initialization."""
|
||||||
docs, _ = client.get('/apidocs')
|
docs, _ = client.get('/apidocs')
|
||||||
assert set(docs['paths'].keys()) == {
|
assert set(docs['paths'].keys()) == {
|
||||||
|
'/',
|
||||||
'/actions/',
|
'/actions/',
|
||||||
'/apidocs',
|
|
||||||
'/allocates/',
|
'/allocates/',
|
||||||
|
'/apidocs',
|
||||||
'/deallocates/',
|
'/deallocates/',
|
||||||
'/deliverynotes/',
|
'/deliverynotes/',
|
||||||
'/devices/',
|
'/devices/',
|
||||||
'/devices/static/{filename}',
|
'/devices/static/{filename}',
|
||||||
'/documents/static/{filename}',
|
|
||||||
'/documents/actions/',
|
'/documents/actions/',
|
||||||
'/documents/erasures/',
|
|
||||||
'/documents/devices/',
|
|
||||||
'/documents/stamps/',
|
|
||||||
'/documents/wbconf/{wbtype}',
|
|
||||||
'/documents/internalstats/',
|
|
||||||
'/documents/stock/',
|
|
||||||
'/documents/check/',
|
'/documents/check/',
|
||||||
|
'/documents/devices/',
|
||||||
|
'/documents/erasures/',
|
||||||
|
'/documents/internalstats/',
|
||||||
'/documents/lots/',
|
'/documents/lots/',
|
||||||
'/versions/',
|
'/documents/stamps/',
|
||||||
'/manufacturers/',
|
'/documents/static/{filename}',
|
||||||
|
'/documents/stock/',
|
||||||
|
'/documents/wbconf/{wbtype}',
|
||||||
|
'/inventory/action/add/',
|
||||||
|
'/inventory/action/allocate/add/',
|
||||||
|
'/inventory/action/datawipe/add/',
|
||||||
|
'/inventory/action/trade/add/',
|
||||||
|
'/inventory/device/',
|
||||||
|
'/inventory/device/add/',
|
||||||
|
'/inventory/device/{id}/',
|
||||||
|
'/inventory/export/{export_id}/',
|
||||||
|
'/inventory/lot/add/',
|
||||||
|
'/inventory/lot/{id}/',
|
||||||
|
'/inventory/lot/{id}/del/',
|
||||||
|
'/inventory/lot/{lot_id}/device/',
|
||||||
|
'/inventory/lot/{lot_id}/device/add/',
|
||||||
|
'/inventory/lot/{lot_id}/trade-document/add/',
|
||||||
|
'/inventory/lot/{lot_id}/upload-snapshot/',
|
||||||
|
'/inventory/tag/devices/add/',
|
||||||
|
'/inventory/tag/devices/{id}/del/',
|
||||||
|
'/inventory/upload-snapshot/',
|
||||||
|
'/labels/',
|
||||||
|
'/labels/add/',
|
||||||
|
'/labels/print',
|
||||||
|
'/labels/unnamed/add/',
|
||||||
|
'/labels/{id}/',
|
||||||
'/licences/',
|
'/licences/',
|
||||||
'/lives/',
|
'/lives/',
|
||||||
|
'/login/',
|
||||||
|
'/logout/',
|
||||||
'/lots/',
|
'/lots/',
|
||||||
'/lots/{id}/children',
|
'/lots/{id}/children',
|
||||||
'/lots/{id}/devices',
|
'/lots/{id}/devices',
|
||||||
|
'/manufacturers/',
|
||||||
'/metrics/',
|
'/metrics/',
|
||||||
|
'/profile/',
|
||||||
|
'/set_password/',
|
||||||
'/tags/',
|
'/tags/',
|
||||||
'/tags/{tag_id}/device/{device_id}',
|
'/tags/{tag_id}/device/{device_id}',
|
||||||
'/trade-documents/',
|
'/trade-documents/',
|
||||||
'/users/',
|
'/users/',
|
||||||
'/users/login/',
|
'/users/login/',
|
||||||
'/users/logout/',
|
'/users/logout/',
|
||||||
|
'/versions/',
|
||||||
}
|
}
|
||||||
assert docs['info'] == {'title': 'Devicehub', 'version': '0.2'}
|
assert docs['info'] == {'title': 'Devicehub', 'version': '0.2'}
|
||||||
assert docs['components']['securitySchemes']['bearerAuth'] == {
|
assert docs['components']['securitySchemes']['bearerAuth'] == {
|
||||||
|
@ -67,6 +95,6 @@ def test_api_docs(client: Client):
|
||||||
'description:': 'HTTP Basic scheme',
|
'description:': 'HTTP Basic scheme',
|
||||||
'type': 'http',
|
'type': 'http',
|
||||||
'scheme': 'basic',
|
'scheme': 'basic',
|
||||||
'name': 'Authorization'
|
'name': 'Authorization',
|
||||||
}
|
}
|
||||||
assert len(docs['definitions']) == 132
|
assert len(docs['definitions']) == 132
|
||||||
|
|
|
@ -0,0 +1,856 @@
|
||||||
|
import csv
|
||||||
|
import json
|
||||||
|
from io import BytesIO
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from flask.testing import FlaskClient
|
||||||
|
from flask_wtf.csrf import generate_csrf
|
||||||
|
|
||||||
|
from ereuse_devicehub.client import UserClient, UserClientFlask
|
||||||
|
from ereuse_devicehub.devicehub import Devicehub
|
||||||
|
from ereuse_devicehub.resources.action.models import Snapshot
|
||||||
|
from ereuse_devicehub.resources.device.models import Device
|
||||||
|
from ereuse_devicehub.resources.lot.models import Lot
|
||||||
|
from tests import conftest
|
||||||
|
|
||||||
|
|
||||||
|
def create_device(user, file_name):
|
||||||
|
uri = '/inventory/upload-snapshot/'
|
||||||
|
snapshot = conftest.yaml2json(file_name.split(".json")[0])
|
||||||
|
b_snapshot = bytes(json.dumps(snapshot), 'utf-8')
|
||||||
|
file_snap = (BytesIO(b_snapshot), file_name)
|
||||||
|
user.get(uri)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'snapshot': file_snap,
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
}
|
||||||
|
user.post(uri, data=data, content_type="multipart/form-data")
|
||||||
|
|
||||||
|
return Snapshot.query.one()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_login(user: UserClient, app: Devicehub):
|
||||||
|
"""Checks a simple login"""
|
||||||
|
|
||||||
|
client = FlaskClient(app, use_cookies=True)
|
||||||
|
|
||||||
|
body, status, headers = client.get('/login/')
|
||||||
|
body = next(body).decode("utf-8")
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert "Login to Your Account" in body
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'email': user.email,
|
||||||
|
'password': 'foo',
|
||||||
|
'remember': False,
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
}
|
||||||
|
body, status, headers = client.post('/login/', data=data, follow_redirects=True)
|
||||||
|
|
||||||
|
body = next(body).decode("utf-8")
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert "Login to Your Account" not in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_profile(user3: UserClientFlask):
|
||||||
|
body, status = user3.get('/profile/')
|
||||||
|
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert "Profile" in body
|
||||||
|
assert user3.email in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_inventory(user3: UserClientFlask):
|
||||||
|
body, status = user3.get('/inventory/device/')
|
||||||
|
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert "Unassgined" in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_add_lot(user3: UserClientFlask):
|
||||||
|
body, status = user3.get('/inventory/lot/add/')
|
||||||
|
|
||||||
|
lot_name = "lot1"
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert "Add a new lot" in body
|
||||||
|
assert lot_name not in body
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'name': lot_name,
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
}
|
||||||
|
body, status = user3.post('/inventory/lot/add/', data=data)
|
||||||
|
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert lot_name in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_del_lot(user3: UserClientFlask):
|
||||||
|
body, status = user3.get('/inventory/lot/add/')
|
||||||
|
|
||||||
|
lot_name = "lot1"
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert "Add a new lot" in body
|
||||||
|
assert lot_name not in body
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'name': lot_name,
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
}
|
||||||
|
body, status = user3.post('/inventory/lot/add/', data=data)
|
||||||
|
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert lot_name in body
|
||||||
|
|
||||||
|
lot = Lot.query.filter_by(name=lot_name).one()
|
||||||
|
uri = '/inventory/lot/{id}/del/'.format(id=lot.id)
|
||||||
|
body, status = user3.get(uri)
|
||||||
|
assert lot_name not in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_update_lot(user3: UserClientFlask):
|
||||||
|
user3.get('/inventory/lot/add/')
|
||||||
|
|
||||||
|
# Add lot
|
||||||
|
data = {
|
||||||
|
'name': "lot1",
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
}
|
||||||
|
user3.post('/inventory/lot/add/', data=data)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'name': "lot2",
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
}
|
||||||
|
|
||||||
|
lot = Lot.query.one()
|
||||||
|
uri = '/inventory/lot/{uuid}/'.format(uuid=lot.id)
|
||||||
|
body, status = user3.post(uri, data=data)
|
||||||
|
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert "lot2" in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_upload_snapshot(user3: UserClientFlask):
|
||||||
|
uri = '/inventory/upload-snapshot/'
|
||||||
|
file_name = 'real-eee-1001pxd.snapshot.12.json'
|
||||||
|
body, status = user3.get(uri)
|
||||||
|
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert "Select a Snapshot file" in body
|
||||||
|
|
||||||
|
snapshot = conftest.yaml2json(file_name.split(".json")[0])
|
||||||
|
b_snapshot = bytes(json.dumps(snapshot), 'utf-8')
|
||||||
|
file_snap = (BytesIO(b_snapshot), file_name)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'snapshot': file_snap,
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
}
|
||||||
|
body, status = user3.post(uri, data=data, content_type="multipart/form-data")
|
||||||
|
|
||||||
|
txt = f"{file_name}: Ok"
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert txt in body
|
||||||
|
db_snapthot = Snapshot.query.one()
|
||||||
|
dev = db_snapthot.device
|
||||||
|
assert str(db_snapthot.uuid) == snapshot['uuid']
|
||||||
|
assert dev.type == 'Laptop'
|
||||||
|
assert dev.serial_number == 'b8oaas048285'
|
||||||
|
assert len(dev.actions) == 12
|
||||||
|
assert len(dev.components) == 9
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_inventory_with_device(user3: UserClientFlask):
|
||||||
|
db_snapthot = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
body, status = user3.get('/inventory/device/')
|
||||||
|
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert "Unassgined" in body
|
||||||
|
assert db_snapthot.device.devicehub_id in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_inventory_filter(user3: UserClientFlask):
|
||||||
|
db_snapthot = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
|
||||||
|
csrf = generate_csrf()
|
||||||
|
body, status = user3.get(f'/inventory/device/?filter=Laptop&csrf_token={csrf}')
|
||||||
|
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert "Unassgined" in body
|
||||||
|
assert db_snapthot.device.devicehub_id in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_export_devices(user3: UserClientFlask):
|
||||||
|
snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
uri = "/inventory/export/devices/?ids={id}".format(id=snap.device.devicehub_id)
|
||||||
|
|
||||||
|
body, status = user3.get(uri)
|
||||||
|
assert status == '200 OK'
|
||||||
|
|
||||||
|
export_csv = [line.split(";") for line in body.split("\n")]
|
||||||
|
|
||||||
|
with Path(__file__).parent.joinpath('files').joinpath(
|
||||||
|
'export_devices.csv'
|
||||||
|
).open() as csv_file:
|
||||||
|
obj_csv = csv.reader(csv_file, delimiter=';', quotechar='"')
|
||||||
|
fixture_csv = list(obj_csv)
|
||||||
|
|
||||||
|
assert fixture_csv[0] == export_csv[0], 'Headers are not equal'
|
||||||
|
assert (
|
||||||
|
fixture_csv[1][:19] == export_csv[1][:19]
|
||||||
|
), 'Computer information are not equal'
|
||||||
|
assert fixture_csv[1][20] == export_csv[1][20], 'Computer information are not equal'
|
||||||
|
assert (
|
||||||
|
fixture_csv[1][22:82] == export_csv[1][22:82]
|
||||||
|
), 'Computer information are not equal'
|
||||||
|
assert fixture_csv[1][83] == export_csv[1][83], 'Computer information are not equal'
|
||||||
|
assert (
|
||||||
|
fixture_csv[1][86:] == export_csv[1][86:]
|
||||||
|
), 'Computer information are not equal'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_export_metrics(user3: UserClientFlask):
|
||||||
|
snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
uri = "/inventory/export/metrics/?ids={id}".format(id=snap.device.devicehub_id)
|
||||||
|
|
||||||
|
body, status = user3.get(uri)
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert body == ''
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_export_links(user3: UserClientFlask):
|
||||||
|
snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
uri = "/inventory/export/links/?ids={id}".format(id=snap.device.devicehub_id)
|
||||||
|
|
||||||
|
body, status = user3.get(uri)
|
||||||
|
assert status == '200 OK'
|
||||||
|
body = body.split("\n")
|
||||||
|
assert ['links', 'http://localhost/devices/O48N2', ''] == body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_export_certificates(user3: UserClientFlask):
|
||||||
|
snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
uri = "/inventory/export/certificates/?ids={id}".format(id=snap.device.devicehub_id)
|
||||||
|
|
||||||
|
body, status = user3.get(uri, decode=False)
|
||||||
|
body = str(next(body))
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert "PDF-1.5" in body
|
||||||
|
assert 'hts54322' in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_labels(user3: UserClientFlask):
|
||||||
|
body, status = user3.get('/labels/')
|
||||||
|
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert "Unique Identifiers Management" in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_add_tag(user3: UserClientFlask):
|
||||||
|
uri = '/labels/add/'
|
||||||
|
body, status = user3.get(uri)
|
||||||
|
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert "Add a new Unique Identifier" in body
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'code': "tag1",
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
}
|
||||||
|
body, status = user3.post(uri, data=data)
|
||||||
|
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert "tag1" in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_label_details(user3: UserClientFlask):
|
||||||
|
uri = '/labels/add/'
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'code': "tag1",
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
}
|
||||||
|
user3.post(uri, data=data)
|
||||||
|
|
||||||
|
body, status = user3.get('/labels/tag1/')
|
||||||
|
assert "tag1" in body
|
||||||
|
assert "Print Label" in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_link_tag_to_device(user3: UserClientFlask):
|
||||||
|
snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
dev = snap.device
|
||||||
|
uri = '/labels/add/'
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'code': "tag1",
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
}
|
||||||
|
user3.post(uri, data=data)
|
||||||
|
|
||||||
|
body, status = user3.get('/inventory/device/')
|
||||||
|
assert "tag1" in body
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'tag': "tag1",
|
||||||
|
'device': dev.id,
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/inventory/tag/devices/add/'
|
||||||
|
user3.post(uri, data=data)
|
||||||
|
assert len(list(dev.tags)) == 2
|
||||||
|
tags = [tag.id for tag in dev.tags]
|
||||||
|
assert "tag1" in tags
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_unlink_tag_to_device(user3: UserClientFlask):
|
||||||
|
# create device
|
||||||
|
snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
dev = snap.device
|
||||||
|
|
||||||
|
# create tag
|
||||||
|
uri = '/labels/add/'
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'code': "tag1",
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
}
|
||||||
|
user3.post(uri, data=data)
|
||||||
|
|
||||||
|
# link tag to device
|
||||||
|
data = {
|
||||||
|
'tag': "tag1",
|
||||||
|
'device': dev.id,
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/inventory/tag/devices/add/'
|
||||||
|
user3.post(uri, data=data)
|
||||||
|
|
||||||
|
# unlink tag to device
|
||||||
|
uri = '/inventory/tag/devices/{id}/del/'.format(id=dev.id)
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'code': "tag1",
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
}
|
||||||
|
user3.post(uri, data=data)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'tag': "tag1",
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
}
|
||||||
|
|
||||||
|
user3.post(uri, data=data)
|
||||||
|
assert len(list(dev.tags)) == 1
|
||||||
|
tag = list(dev.tags)[0]
|
||||||
|
assert not tag.id == "tag1"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_print_labels(user3: UserClientFlask):
|
||||||
|
# create device
|
||||||
|
snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
dev = snap.device
|
||||||
|
|
||||||
|
# create tag
|
||||||
|
uri = '/labels/add/'
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'code': "tag1",
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
}
|
||||||
|
user3.post(uri, data=data)
|
||||||
|
|
||||||
|
# link tag to device
|
||||||
|
data = {
|
||||||
|
'tag': "tag1",
|
||||||
|
'device': dev.id,
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/inventory/tag/devices/add/'
|
||||||
|
user3.post(uri, data=data)
|
||||||
|
|
||||||
|
assert len(list(dev.tags)) == 2
|
||||||
|
|
||||||
|
uri = '/labels/print'
|
||||||
|
data = {
|
||||||
|
'devices': "{}".format(dev.id),
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
}
|
||||||
|
body, status = user3.post(uri, data=data)
|
||||||
|
|
||||||
|
assert status == '200 OK'
|
||||||
|
path = "/inventory/device/{}/".format(dev.devicehub_id)
|
||||||
|
assert path in body
|
||||||
|
assert "tag1" not in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_add_monitor(user3: UserClientFlask):
|
||||||
|
uri = '/inventory/device/add/'
|
||||||
|
body, status = user3.get(uri)
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert "New Device" in body
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
'type': "Monitor",
|
||||||
|
'serial_number': "AAAAB",
|
||||||
|
'model': "LC27T55",
|
||||||
|
'manufacturer': "Samsung",
|
||||||
|
'generation': 1,
|
||||||
|
'weight': 0.1,
|
||||||
|
'height': 0.1,
|
||||||
|
'depth': 0.1,
|
||||||
|
}
|
||||||
|
body, status = user3.post(uri, data=data)
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert 'Device "Monitor" created successfully!' in body
|
||||||
|
dev = Device.query.one()
|
||||||
|
assert dev.type == 'Monitor'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_filter_monitor(user3: UserClientFlask):
|
||||||
|
uri = '/inventory/device/add/'
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
'type': "Monitor",
|
||||||
|
'serial_number': "AAAAB",
|
||||||
|
'model': "LC27T55",
|
||||||
|
'manufacturer': "Samsung",
|
||||||
|
'generation': 1,
|
||||||
|
'weight': 0.1,
|
||||||
|
'height': 0.1,
|
||||||
|
'depth': 0.1,
|
||||||
|
}
|
||||||
|
user3.post(uri, data=data)
|
||||||
|
csrf = generate_csrf()
|
||||||
|
|
||||||
|
uri = f'/inventory/device/?filter=Monitor&csrf_token={csrf}'
|
||||||
|
body, status = user3.get(uri)
|
||||||
|
|
||||||
|
assert status == '200 OK'
|
||||||
|
dev = Device.query.one()
|
||||||
|
assert dev.devicehub_id in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_action_recycling(user3: UserClientFlask):
|
||||||
|
snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
dev = snap.device
|
||||||
|
uri = '/inventory/device/'
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
# fail request
|
||||||
|
data = {
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
'type': "Allocate",
|
||||||
|
'severity': "Info",
|
||||||
|
'devices': "{}".format(dev.id),
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/inventory/action/add/'
|
||||||
|
body, status = user3.post(uri, data=data)
|
||||||
|
assert dev.actions[-1].type == 'EreusePrice'
|
||||||
|
assert 'Action Allocate error!' in body
|
||||||
|
|
||||||
|
# good request
|
||||||
|
data = {
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
'type': "Recycling",
|
||||||
|
'severity': "Info",
|
||||||
|
'devices': "{}".format(dev.id),
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/inventory/action/add/'
|
||||||
|
body, status = user3.post(uri, data=data)
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert dev.actions[-1].type == 'Recycling'
|
||||||
|
assert 'Action "Recycling" created successfully!' in body
|
||||||
|
assert dev.devicehub_id in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_action_error_without_devices(user3: UserClientFlask):
|
||||||
|
uri = '/inventory/device/'
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
'type': "Recycling",
|
||||||
|
'severity': "Info",
|
||||||
|
'devices': "",
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/inventory/action/add/'
|
||||||
|
body, status = user3.post(uri, data=data)
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert 'Action Recycling error!' in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_action_use(user3: UserClientFlask):
|
||||||
|
snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
dev = snap.device
|
||||||
|
uri = '/inventory/device/'
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
'type': "Use",
|
||||||
|
'severity': "Info",
|
||||||
|
'devices': "{}".format(dev.id),
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/inventory/action/add/'
|
||||||
|
body, status = user3.post(uri, data=data)
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert dev.actions[-1].type == 'Use'
|
||||||
|
assert 'Action "Use" created successfully!' in body
|
||||||
|
assert dev.devicehub_id in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_action_refurbish(user3: UserClientFlask):
|
||||||
|
snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
dev = snap.device
|
||||||
|
uri = '/inventory/device/'
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
'type': "Refurbish",
|
||||||
|
'severity': "Info",
|
||||||
|
'devices': "{}".format(dev.id),
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/inventory/action/add/'
|
||||||
|
body, status = user3.post(uri, data=data)
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert dev.actions[-1].type == 'Refurbish'
|
||||||
|
assert 'Action "Refurbish" created successfully!' in body
|
||||||
|
assert dev.devicehub_id in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_action_management(user3: UserClientFlask):
|
||||||
|
snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
dev = snap.device
|
||||||
|
uri = '/inventory/device/'
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
'type': "Management",
|
||||||
|
'severity': "Info",
|
||||||
|
'devices': "{}".format(dev.id),
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/inventory/action/add/'
|
||||||
|
body, status = user3.post(uri, data=data)
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert dev.actions[-1].type == 'Management'
|
||||||
|
assert 'Action "Management" created successfully!' in body
|
||||||
|
assert dev.devicehub_id in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_action_allocate(user3: UserClientFlask):
|
||||||
|
snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
dev = snap.device
|
||||||
|
uri = '/inventory/device/'
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
'type': "Allocate",
|
||||||
|
'severity': "Info",
|
||||||
|
'devices': "{}".format(dev.id),
|
||||||
|
'start_time': '2000-01-01',
|
||||||
|
'end_time': '2000-06-01',
|
||||||
|
'end_users': 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/inventory/action/allocate/add/'
|
||||||
|
body, status = user3.post(uri, data=data)
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert dev.actions[-1].type == 'Allocate'
|
||||||
|
assert 'Action "Allocate" created successfully!' in body
|
||||||
|
assert dev.devicehub_id in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_action_allocate_error_required(user3: UserClientFlask):
|
||||||
|
snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
dev = snap.device
|
||||||
|
uri = '/inventory/device/'
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
'type': "Trade",
|
||||||
|
'severity': "Info",
|
||||||
|
'devices': "{}".format(dev.id),
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/inventory/action/allocate/add/'
|
||||||
|
body, status = user3.post(uri, data=data)
|
||||||
|
assert dev.actions[-1].type != 'Allocate'
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
'type': "Allocate",
|
||||||
|
'severity': "Info",
|
||||||
|
'devices': "{}".format(dev.id),
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/inventory/action/allocate/add/'
|
||||||
|
body, status = user3.post(uri, data=data)
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert 'Action Allocate error' in body
|
||||||
|
assert 'You need to specify a number of users!' in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_action_allocate_error_dates(user3: UserClientFlask):
|
||||||
|
snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
dev = snap.device
|
||||||
|
uri = '/inventory/device/'
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
'type': "Allocate",
|
||||||
|
'severity': "Info",
|
||||||
|
'devices': "{}".format(dev.id),
|
||||||
|
'start_time': '2000-06-01',
|
||||||
|
'end_time': '2000-01-01',
|
||||||
|
'end_users': 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/inventory/action/allocate/add/'
|
||||||
|
body, status = user3.post(uri, data=data)
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert 'Action Allocate error' in body
|
||||||
|
assert 'The action cannot finish before it starts.' in body
|
||||||
|
assert dev.actions[-1].type != 'Allocate'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_action_deallocate(user3: UserClientFlask):
|
||||||
|
snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
dev = snap.device
|
||||||
|
uri = '/inventory/device/'
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
'type': "Allocate",
|
||||||
|
'severity': "Info",
|
||||||
|
'devices': "{}".format(dev.id),
|
||||||
|
'start_time': '2000-01-01',
|
||||||
|
'end_time': '2000-06-01',
|
||||||
|
'end_users': 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/inventory/action/allocate/add/'
|
||||||
|
|
||||||
|
user3.post(uri, data=data)
|
||||||
|
assert dev.actions[-1].type == 'Allocate'
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
'type': "Deallocate",
|
||||||
|
'severity': "Info",
|
||||||
|
'devices': "{}".format(dev.id),
|
||||||
|
'start_time': '2000-01-01',
|
||||||
|
'end_time': '2000-06-01',
|
||||||
|
'end_users': 2,
|
||||||
|
}
|
||||||
|
body, status = user3.post(uri, data=data)
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert dev.actions[-1].type == 'Deallocate'
|
||||||
|
assert 'Action "Deallocate" created successfully!' in body
|
||||||
|
assert dev.devicehub_id in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_action_toprepare(user3: UserClientFlask):
|
||||||
|
snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
dev = snap.device
|
||||||
|
uri = '/inventory/device/'
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
'type': "ToPrepare",
|
||||||
|
'severity': "Info",
|
||||||
|
'devices': "{}".format(dev.id),
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/inventory/action/add/'
|
||||||
|
body, status = user3.post(uri, data=data)
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert dev.actions[-1].type == 'ToPrepare'
|
||||||
|
assert 'Action "ToPrepare" created successfully!' in body
|
||||||
|
assert dev.devicehub_id in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_action_prepare(user3: UserClientFlask):
|
||||||
|
snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
dev = snap.device
|
||||||
|
uri = '/inventory/device/'
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
'type': "Prepare",
|
||||||
|
'severity': "Info",
|
||||||
|
'devices': "{}".format(dev.id),
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/inventory/action/add/'
|
||||||
|
body, status = user3.post(uri, data=data)
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert dev.actions[-1].type == 'Prepare'
|
||||||
|
assert 'Action "Prepare" created successfully!' in body
|
||||||
|
assert dev.devicehub_id in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_action_torepair(user3: UserClientFlask):
|
||||||
|
snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
dev = snap.device
|
||||||
|
uri = '/inventory/device/'
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
'type': "ToRepair",
|
||||||
|
'severity': "Info",
|
||||||
|
'devices': "{}".format(dev.id),
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/inventory/action/add/'
|
||||||
|
body, status = user3.post(uri, data=data)
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert dev.actions[-1].type == 'ToRepair'
|
||||||
|
assert 'Action "ToRepair" created successfully!' in body
|
||||||
|
assert dev.devicehub_id in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_action_ready(user3: UserClientFlask):
|
||||||
|
snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
dev = snap.device
|
||||||
|
uri = '/inventory/device/'
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
'type': "Ready",
|
||||||
|
'severity': "Info",
|
||||||
|
'devices': "{}".format(dev.id),
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/inventory/action/add/'
|
||||||
|
body, status = user3.post(uri, data=data)
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert dev.actions[-1].type == 'Ready'
|
||||||
|
assert 'Action "Ready" created successfully!' in body
|
||||||
|
assert dev.devicehub_id in body
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_action_datawipe(user3: UserClientFlask):
|
||||||
|
snap = create_device(user3, 'real-eee-1001pxd.snapshot.12.json')
|
||||||
|
dev = snap.device
|
||||||
|
uri = '/inventory/device/'
|
||||||
|
user3.get(uri)
|
||||||
|
|
||||||
|
b_file = b'1234567890'
|
||||||
|
file_name = "my_file.doc"
|
||||||
|
file_upload = (BytesIO(b_file), file_name)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'csrf_token': generate_csrf(),
|
||||||
|
'type': "DataWipe",
|
||||||
|
'severity': "Info",
|
||||||
|
'devices': "{}".format(dev.id),
|
||||||
|
'document-file_name': file_upload,
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = '/inventory/action/datawipe/add/'
|
||||||
|
body, status = user3.post(uri, data=data, content_type="multipart/form-data")
|
||||||
|
assert status == '200 OK'
|
||||||
|
assert dev.actions[-1].type == 'DataWipe'
|
||||||
|
assert 'Action "DataWipe" created successfully!' in body
|
||||||
|
assert dev.devicehub_id in body
|
Reference in New Issue