diff --git a/ereuse_devicehub/resources/documents/device_row.py b/ereuse_devicehub/resources/documents/device_row.py index dea1e698..ecdc93d9 100644 --- a/ereuse_devicehub/resources/documents/device_row.py +++ b/ereuse_devicehub/resources/documents/device_row.py @@ -30,31 +30,23 @@ class DeviceRow(OrderedDict): self['Tag 1'] = self['Tag 2'] = self['Tag 3'] = '' for i, tag in zip(range(1, 3), device.tags): self['Tag {}'.format(i)] = format(tag) - self['Serial Number'] = device.serial_number - self['Model'] = device.model - self['Manufacturer'] = device.manufacturer - # self['State'] = device.last_action_of() + 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: + except LookupError: self['Physical state'] = '' try: self['Trading state'] = device.last_action_of(*states.Trading.actions()).t - except: + except LookupError: self['Trading state'] = '' - try: - self['Price'] = device.price - except: - self['Price'] = '' + self['Price'] = convert_none_to_empty_str(device.price) if isinstance(device, d.Computer): - self['Processor'] = device.processor_model - self['RAM (MB)'] = device.ram_size - self['Data Storage Size (MB)'] = 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 + 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 @@ -135,3 +127,47 @@ class DeviceRow(OrderedDict): self['{} {} Speed (MHz)'.format(type, i)] = component.speed # 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 diff --git a/ereuse_devicehub/resources/documents/documents.py b/ereuse_devicehub/resources/documents/documents.py index e66ed012..29349682 100644 --- a/ereuse_devicehub/resources/documents/documents.py +++ b/ereuse_devicehub/resources/documents/documents.py @@ -10,7 +10,7 @@ import flask import flask_weasyprint import teal.marshmallow from boltons import urlutils -from flask import make_response +from flask import make_response, g from teal.cache import cache 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.device import models as devs 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): @@ -106,7 +107,7 @@ class DocumentView(DeviceView): class DevicesDocumentView(DeviceView): @cache(datetime.timedelta(minutes=1)) 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) def generate_post_csv(self, query): @@ -129,7 +130,7 @@ class DevicesDocumentView(DeviceView): class StockDocumentView(DeviceView): # @cache(datetime.timedelta(minutes=1)) 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) def generate_post_csv(self, query): @@ -138,13 +139,13 @@ class StockDocumentView(DeviceView): cw = csv.writer(data) first = True for device in query: - d = DeviceRow(device) + d = StockRow(device) if first: cw.writerow(d.keys()) first = False cw.writerow(d.values()) 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' return output @@ -183,8 +184,11 @@ class DocumentDef(Resource): devices_view = DevicesDocumentView.as_view('devicesDocumentView', definition=self, auth=app.auth) - 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) stock_view = StockDocumentView.as_view('stockDocumentView', definition=self, auth=app.auth) diff --git a/tests/conftest.py b/tests/conftest.py index dc43bb6f..be01b87f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -93,6 +93,18 @@ def user(app: Devicehub) -> UserClient: 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: user = User(email=email, password=password) user.individuals.add(Person(name='Timmy')) diff --git a/tests/files/basic-stock.csv b/tests/files/basic-stock.csv new file mode 100644 index 00000000..d037b191 --- /dev/null +++ b/tests/files/basic-stock.csv @@ -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 diff --git a/tests/files/basic.snapshot2.yaml b/tests/files/basic.snapshot2.yaml new file mode 100644 index 00000000..70bfaa34 --- /dev/null +++ b/tests/files/basic.snapshot2.yaml @@ -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 diff --git a/tests/test_documents.py b/tests/test_documents.py index 81a83bd8..30bde2ed 100644 --- a/tests/test_documents.py +++ b/tests/test_documents.py @@ -1,11 +1,12 @@ -import pytest -import teal.marshmallow -from ereuse_utils.test import ANY import csv from datetime import datetime from io import StringIO 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.resources.action.models import Snapshot from ereuse_devicehub.resources.documents import documents @@ -223,4 +224,37 @@ def test_export_multiple_different_devices(user: UserClient): for row in export_csv: 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'