Merge pull request #48 from eReuse/feature/46-device-stock-control
Feature/46-device-stock-control
This commit is contained in:
commit
24578e7188
|
@ -30,31 +30,23 @@ class DeviceRow(OrderedDict):
|
||||||
self['Tag 1'] = self['Tag 2'] = self['Tag 3'] = ''
|
self['Tag 1'] = self['Tag 2'] = self['Tag 3'] = ''
|
||||||
for i, tag in zip(range(1, 3), device.tags):
|
for i, tag in zip(range(1, 3), device.tags):
|
||||||
self['Tag {}'.format(i)] = format(tag)
|
self['Tag {}'.format(i)] = format(tag)
|
||||||
self['Serial Number'] = device.serial_number
|
self['Serial Number'] = convert_none_to_empty_str(device.serial_number)
|
||||||
self['Model'] = device.model
|
self['Model'] = convert_none_to_empty_str(device.model)
|
||||||
self['Manufacturer'] = device.manufacturer
|
self['Manufacturer'] = convert_none_to_empty_str(device.manufacturer)
|
||||||
# self['State'] = device.last_action_of()
|
|
||||||
self['Registered in'] = format(device.created, '%c')
|
self['Registered in'] = format(device.created, '%c')
|
||||||
try:
|
try:
|
||||||
self['Physical state'] = device.last_action_of(*states.Physical.actions()).t
|
self['Physical state'] = device.last_action_of(*states.Physical.actions()).t
|
||||||
except:
|
except LookupError:
|
||||||
self['Physical state'] = ''
|
self['Physical state'] = ''
|
||||||
try:
|
try:
|
||||||
self['Trading state'] = device.last_action_of(*states.Trading.actions()).t
|
self['Trading state'] = device.last_action_of(*states.Trading.actions()).t
|
||||||
except:
|
except LookupError:
|
||||||
self['Trading state'] = ''
|
self['Trading state'] = ''
|
||||||
try:
|
self['Price'] = convert_none_to_empty_str(device.price)
|
||||||
self['Price'] = device.price
|
|
||||||
except:
|
|
||||||
self['Price'] = ''
|
|
||||||
if isinstance(device, d.Computer):
|
if isinstance(device, d.Computer):
|
||||||
self['Processor'] = device.processor_model
|
self['Processor'] = convert_none_to_empty_str(device.processor_model)
|
||||||
self['RAM (MB)'] = device.ram_size
|
self['RAM (MB)'] = convert_none_to_empty_str(device.ram_size)
|
||||||
self['Data Storage Size (MB)'] = device.data_storage_size
|
self['Data Storage Size (MB)'] = convert_none_to_empty_str(device.data_storage_size)
|
||||||
if isinstance(device, d.Mobile):
|
|
||||||
self['Display Size'] = device.display_size
|
|
||||||
self['RAM (MB)'] = device.ram_size
|
|
||||||
self['Data Storage Size (MB)'] = device.data_storage_size
|
|
||||||
rate = device.rate
|
rate = device.rate
|
||||||
if rate:
|
if rate:
|
||||||
self['Rate'] = rate.rating
|
self['Rate'] = rate.rating
|
||||||
|
@ -135,3 +127,47 @@ class DeviceRow(OrderedDict):
|
||||||
self['{} {} Speed (MHz)'.format(type, i)] = component.speed
|
self['{} {} Speed (MHz)'.format(type, i)] = component.speed
|
||||||
|
|
||||||
# todo add Display, NetworkAdapter, etc...
|
# todo add Display, NetworkAdapter, etc...
|
||||||
|
|
||||||
|
|
||||||
|
class StockRow(OrderedDict):
|
||||||
|
def __init__(self, device: d.Device) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.device = device
|
||||||
|
self['Type'] = convert_none_to_empty_str(device.t)
|
||||||
|
if isinstance(device, d.Computer):
|
||||||
|
self['Chassis'] = device.chassis
|
||||||
|
else:
|
||||||
|
self['Chassis'] = ''
|
||||||
|
self['Serial Number'] = convert_none_to_empty_str(device.serial_number)
|
||||||
|
self['Model'] = convert_none_to_empty_str(device.model)
|
||||||
|
self['Manufacturer'] = convert_none_to_empty_str(device.manufacturer)
|
||||||
|
self['Registered in'] = format(device.created, '%c')
|
||||||
|
try:
|
||||||
|
self['Physical state'] = device.last_action_of(*states.Physical.actions()).t
|
||||||
|
except LookupError:
|
||||||
|
self['Physical state'] = ''
|
||||||
|
try:
|
||||||
|
self['Trading state'] = device.last_action_of(*states.Trading.actions()).t
|
||||||
|
except LookupError:
|
||||||
|
self['Trading state'] = ''
|
||||||
|
self['Price'] = convert_none_to_empty_str(device.price)
|
||||||
|
self['Processor'] = convert_none_to_empty_str(device.processor_model)
|
||||||
|
self['RAM (MB)'] = convert_none_to_empty_str(device.ram_size)
|
||||||
|
self['Data Storage Size (MB)'] = convert_none_to_empty_str(device.data_storage_size)
|
||||||
|
rate = device.rate
|
||||||
|
if rate:
|
||||||
|
self['Rate'] = rate.rating
|
||||||
|
self['Range'] = rate.rating_range
|
||||||
|
assert isinstance(rate, RateComputer)
|
||||||
|
self['Processor Rate'] = rate.processor
|
||||||
|
self['Processor Range'] = rate.processor_range
|
||||||
|
self['RAM Rate'] = rate.ram
|
||||||
|
self['RAM Range'] = rate.ram_range
|
||||||
|
self['Data Storage Rate'] = rate.data_storage
|
||||||
|
self['Data Storage Range'] = rate.data_storage_range
|
||||||
|
|
||||||
|
|
||||||
|
def convert_none_to_empty_str(s):
|
||||||
|
if s is None:
|
||||||
|
return ''
|
||||||
|
return s
|
||||||
|
|
|
@ -10,7 +10,7 @@ import flask
|
||||||
import flask_weasyprint
|
import flask_weasyprint
|
||||||
import teal.marshmallow
|
import teal.marshmallow
|
||||||
from boltons import urlutils
|
from boltons import urlutils
|
||||||
from flask import make_response
|
from flask import make_response, g
|
||||||
from teal.cache import cache
|
from teal.cache import cache
|
||||||
from teal.resource import Resource
|
from teal.resource import Resource
|
||||||
|
|
||||||
|
@ -18,7 +18,8 @@ from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.resources.action import models as evs
|
from ereuse_devicehub.resources.action import models as evs
|
||||||
from ereuse_devicehub.resources.device import models as devs
|
from ereuse_devicehub.resources.device import models as devs
|
||||||
from ereuse_devicehub.resources.device.views import DeviceView
|
from ereuse_devicehub.resources.device.views import DeviceView
|
||||||
from ereuse_devicehub.resources.documents.device_row import DeviceRow
|
from ereuse_devicehub.resources.documents.device_row import DeviceRow, StockRow
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Format(enum.Enum):
|
class Format(enum.Enum):
|
||||||
|
@ -106,7 +107,7 @@ class DocumentView(DeviceView):
|
||||||
class DevicesDocumentView(DeviceView):
|
class DevicesDocumentView(DeviceView):
|
||||||
@cache(datetime.timedelta(minutes=1))
|
@cache(datetime.timedelta(minutes=1))
|
||||||
def find(self, args: dict):
|
def find(self, args: dict):
|
||||||
query = self.query(args)
|
query = (x for x in self.query(args) if x.owner_id == g.user.id)
|
||||||
return self.generate_post_csv(query)
|
return self.generate_post_csv(query)
|
||||||
|
|
||||||
def generate_post_csv(self, query):
|
def generate_post_csv(self, query):
|
||||||
|
@ -129,7 +130,7 @@ class DevicesDocumentView(DeviceView):
|
||||||
class StockDocumentView(DeviceView):
|
class StockDocumentView(DeviceView):
|
||||||
# @cache(datetime.timedelta(minutes=1))
|
# @cache(datetime.timedelta(minutes=1))
|
||||||
def find(self, args: dict):
|
def find(self, args: dict):
|
||||||
query = self.query(args)
|
query = (x for x in self.query(args) if x.owner_id == g.user.id)
|
||||||
return self.generate_post_csv(query)
|
return self.generate_post_csv(query)
|
||||||
|
|
||||||
def generate_post_csv(self, query):
|
def generate_post_csv(self, query):
|
||||||
|
@ -138,13 +139,13 @@ class StockDocumentView(DeviceView):
|
||||||
cw = csv.writer(data)
|
cw = csv.writer(data)
|
||||||
first = True
|
first = True
|
||||||
for device in query:
|
for device in query:
|
||||||
d = DeviceRow(device)
|
d = StockRow(device)
|
||||||
if first:
|
if first:
|
||||||
cw.writerow(d.keys())
|
cw.writerow(d.keys())
|
||||||
first = False
|
first = False
|
||||||
cw.writerow(d.values())
|
cw.writerow(d.values())
|
||||||
output = make_response(data.getvalue())
|
output = make_response(data.getvalue())
|
||||||
output.headers['Content-Disposition'] = 'attachment; filename=export.csv'
|
output.headers['Content-Disposition'] = 'attachment; filename=devices-stock.csv'
|
||||||
output.headers['Content-type'] = 'text/csv'
|
output.headers['Content-type'] = 'text/csv'
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
@ -183,8 +184,11 @@ class DocumentDef(Resource):
|
||||||
devices_view = DevicesDocumentView.as_view('devicesDocumentView',
|
devices_view = DevicesDocumentView.as_view('devicesDocumentView',
|
||||||
definition=self,
|
definition=self,
|
||||||
auth=app.auth)
|
auth=app.auth)
|
||||||
|
|
||||||
devices_view = app.auth.requires_auth(devices_view)
|
devices_view = app.auth.requires_auth(devices_view)
|
||||||
|
|
||||||
|
stock_view = StockDocumentView.as_view('stockDocumentView', definition=self)
|
||||||
|
stock_view = app.auth.requires_auth(stock_view)
|
||||||
|
|
||||||
self.add_url_rule('/devices/', defaults=d, view_func=devices_view, methods=get)
|
self.add_url_rule('/devices/', defaults=d, view_func=devices_view, methods=get)
|
||||||
|
|
||||||
stock_view = StockDocumentView.as_view('stockDocumentView', definition=self, auth=app.auth)
|
stock_view = StockDocumentView.as_view('stockDocumentView', definition=self, auth=app.auth)
|
||||||
|
|
|
@ -93,6 +93,18 @@ def user(app: Devicehub) -> UserClient:
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def user2(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 = UserClient(app, user.email, password, response_wrapper=app.response_class)
|
||||||
|
client.login()
|
||||||
|
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'))
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Type,Chassis,Serial Number,Model,Manufacturer,Registered in,Physical state,Trading state,Price,Processor,RAM (MB),Data Storage Size (MB),Rate,Range,Processor Rate,Processor Range,RAM Rate,RAM Range,Data Storage Rate,Data Storage Range
|
||||||
|
Desktop,Microtower,d1s,d1ml,d1mr,Tue Jul 2 10:35:10 2019,,,,p1ml,0,0,1.0,Very low,1.0,Very low,1.0,Very low,1.0,Very low
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
type: Snapshot
|
||||||
|
uuid: 123e4567-e89b-12d3-a456-426655440000
|
||||||
|
version: '11.0'
|
||||||
|
software: Workbench
|
||||||
|
elapsed: 4
|
||||||
|
device:
|
||||||
|
type: Desktop
|
||||||
|
chassis: Microtower
|
||||||
|
serialNumber: d2s
|
||||||
|
model: d1ml
|
||||||
|
manufacturer: d1mr
|
||||||
|
actions:
|
||||||
|
- type: VisualTest
|
||||||
|
appearanceRange: A
|
||||||
|
functionalityRange: B
|
||||||
|
components:
|
||||||
|
- type: GraphicCard
|
||||||
|
serialNumber: gc1s
|
||||||
|
model: gc1ml
|
||||||
|
manufacturer: gc1mr
|
||||||
|
- type: RamModule
|
||||||
|
serialNumber: rm1s
|
||||||
|
model: rm1ml
|
||||||
|
manufacturer: rm1mr
|
||||||
|
speed: 1333
|
||||||
|
- type: Processor
|
||||||
|
serialNumber: p1s
|
||||||
|
model: p1mla
|
||||||
|
manufacturer: p1mr
|
||||||
|
speed: 1.6
|
||||||
|
actions:
|
||||||
|
- type: BenchmarkProcessor
|
||||||
|
rate: 2410
|
||||||
|
elapsed: 11
|
|
@ -1,11 +1,12 @@
|
||||||
import pytest
|
|
||||||
import teal.marshmallow
|
|
||||||
from ereuse_utils.test import ANY
|
|
||||||
import csv
|
import csv
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import teal.marshmallow
|
||||||
|
from ereuse_utils.test import ANY
|
||||||
|
|
||||||
from ereuse_devicehub.client import Client, UserClient
|
from ereuse_devicehub.client import Client, UserClient
|
||||||
from ereuse_devicehub.resources.action.models import Snapshot
|
from ereuse_devicehub.resources.action.models import Snapshot
|
||||||
from ereuse_devicehub.resources.documents import documents
|
from ereuse_devicehub.resources.documents import documents
|
||||||
|
@ -223,4 +224,37 @@ def test_export_multiple_different_devices(user: UserClient):
|
||||||
for row in export_csv:
|
for row in export_csv:
|
||||||
del row[8]
|
del row[8]
|
||||||
|
|
||||||
assert fixture_csv[:3] == export_csv
|
assert fixture_csv == export_csv
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
def test_report_devices_stock_control(user: UserClient, user2: UserClient):
|
||||||
|
"""Test export device information in a csv file."""
|
||||||
|
snapshot, _ = user.post(file('basic.snapshot'), res=Snapshot)
|
||||||
|
snapshot2, _ = user2.post(file('basic.snapshot2'), res=Snapshot)
|
||||||
|
|
||||||
|
csv_str, _ = user.get(res=documents.DocumentDef.t,
|
||||||
|
item='stock/',
|
||||||
|
accept='text/csv',
|
||||||
|
query=[('filter', {'type': ['Computer']})])
|
||||||
|
f = StringIO(csv_str)
|
||||||
|
obj_csv = csv.reader(f, f)
|
||||||
|
export_csv = list(obj_csv)
|
||||||
|
|
||||||
|
# Open fixture csv and transform to list
|
||||||
|
with Path(__file__).parent.joinpath('files').joinpath('basic-stock.csv').open() as csv_file:
|
||||||
|
obj_csv = csv.reader(csv_file)
|
||||||
|
fixture_csv = list(obj_csv)
|
||||||
|
|
||||||
|
assert user.user['id'] != user2.user['id']
|
||||||
|
assert len(export_csv) == 2
|
||||||
|
|
||||||
|
assert isinstance(datetime.strptime(export_csv[1][5], '%c'), datetime), \
|
||||||
|
'Register in field is not a datetime'
|
||||||
|
|
||||||
|
# Pop dates fields from csv lists to compare them
|
||||||
|
fixture_csv[1] = fixture_csv[1][:5] + fixture_csv[1][6:]
|
||||||
|
export_csv[1] = export_csv[1][:5] + export_csv[1][6:]
|
||||||
|
|
||||||
|
assert fixture_csv[0] == export_csv[0], 'Headers are not equal'
|
||||||
|
assert fixture_csv[1] == export_csv[1], 'Computer information are not equal'
|
||||||
|
|
Reference in New Issue