Move export to Documents

This commit is contained in:
Xavier Bustamante Talavera 2019-02-28 18:21:24 +01:00
parent 208814ecf2
commit 13ffab7022
5 changed files with 170 additions and 159 deletions

View File

@ -1,14 +1,10 @@
import csv
import datetime import datetime
from io import StringIO
import marshmallow import marshmallow
from flask import current_app as app, render_template, request, make_response from flask import current_app as app, render_template, request
from flask.json import jsonify from flask.json import jsonify
from flask_sqlalchemy import Pagination from flask_sqlalchemy import Pagination
from marshmallow import fields, fields as f, validate as v from marshmallow import fields, fields as f, validate as v
from sqlalchemy.orm import aliased
from sqlalchemy.util import OrderedDict
from teal import query from teal import query
from teal.cache import cache from teal.cache import cache
from teal.resource import View from teal.resource import View
@ -17,9 +13,7 @@ from ereuse_devicehub import auth
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
from ereuse_devicehub.query import SearchQueryParser, things_response from ereuse_devicehub.query import SearchQueryParser, things_response
from ereuse_devicehub.resources import search from ereuse_devicehub.resources import search
from ereuse_devicehub.resources.device.models import Component, Computer, Device, Manufacturer, \ from ereuse_devicehub.resources.device.models import Device, Manufacturer
Display, Processor, GraphicCard, Motherboard, NetworkAdapter, DataStorage, RamModule, \
SoundCard
from ereuse_devicehub.resources.device.search import DeviceSearch from ereuse_devicehub.resources.device.search import DeviceSearch
from ereuse_devicehub.resources.event import models as events from ereuse_devicehub.resources.event import models as events
from ereuse_devicehub.resources.lot.models import LotDeviceDescendants from ereuse_devicehub.resources.lot.models import LotDeviceDescendants
@ -138,150 +132,7 @@ class DeviceView(View):
).order_by( ).order_by(
search.Search.rank(properties, search_p) + search.Search.rank(tags, search_p) search.Search.rank(properties, search_p) + search.Search.rank(tags, search_p)
) )
query = query.filter(*args['filter']).order_by(*args['sort']) return query.filter(*args['filter']).order_by(*args['sort'])
if 'text/csv' in request.accept_mimetypes:
return self.generate_post_csv(query)
else:
devices = query.paginate(page=args['page'], per_page=30) # type: Pagination
ret = {
'items': self.schema.dump(devices.items, many=True, nested=1),
# todo pagination should be in Header like github
# https://developer.github.com/v3/guides/traversing-with-pagination/
'pagination': {
'page': devices.page,
'perPage': devices.per_page,
'total': devices.total,
'previous': devices.prev_num,
'next': devices.next_num
},
'url': request.path
}
return jsonify(ret)
def generate_post_csv(self, query):
"""
Get device query and put information in csv format
:param query:
:return:
"""
data = StringIO()
cw = csv.writer(data)
first = True
for device in query:
d = DeviceRow(device)
if first:
cw.writerow(name for name in d.keys())
first = False
cw.writerow(v for v in d.values())
output = make_response(data.getvalue())
output.headers['Content-Disposition'] = 'attachment; filename=export.csv'
output.headers['Content-type'] = 'text/csv'
return output
class DeviceRow(OrderedDict):
NUMS = {
Display.t: 1,
Processor.t: 2,
GraphicCard.t: 2,
Motherboard.t: 1,
NetworkAdapter.t: 2,
SoundCard.t: 2
}
def __init__(self, device: Device) -> None:
super().__init__()
self.device = device
# General information about device
self['Type'] = device.t
if isinstance(device, Computer):
self['Chassis'] = device.chassis
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_event_of()
self['Price'] = device.price
self['Registered in'] = format(device.created, '%c')
if isinstance(device, Computer):
self['Processor'] = device.processor_model
self['RAM (GB)'] = device.ram_size
self['Storage Size (MB)'] = device.data_storage_size
rate = device.rate
if rate:
self['Rate'] = rate.rating
self['Range'] = rate.rating_range
self['Processor Rate'] = rate.processor
self['Processor Range'] = rate.workbench.processor_range
self['RAM Rate'] = rate.ram
self['RAM Range'] = rate.workbench.ram_range
self['Data Storage Rate'] = rate.data_storage
self['Data Storage Range'] = rate.workbench.data_storage_range
# More specific information about components
if isinstance(device, Computer):
self.components()
def components(self):
"""
Function to get all components information of a device
"""
assert isinstance(self.device, Computer)
# todo put an input specific order (non alphabetic)
for type in sorted(app.resources[Component.t].subresources_types): # type: str
max = self.NUMS.get(type, 4)
if type not in ['Component', 'HardDrive', 'SolidStateDrive']:
i = 1
for component in (r for r in self.device.components if r.type == type):
self.fill_component(type, i, component)
i += 1
if i > max:
break
while i <= max:
self.fill_component(type, i)
i += 1
def fill_component(self, type, i, component=None):
"""
Function to put specific information of components in OrderedDict (csv)
:param type: type of component
:param component: device.components
"""
self['{} {}'.format(type, i)] = format(component) if component else ''
self['{} {} Manufacturer'.format(type, i)] = component.serial_number if component else ''
self['{} {} Model'.format(type, i)] = component.serial_number if component else ''
self['{} {} Serial Number'.format(type, i)] = component.serial_number if component else ''
""" Particular fields for component GraphicCard """
if isinstance(component, GraphicCard):
self['{} {} Memory (MB)'.format(type, i)] = component.memory
""" Particular fields for component DataStorage.t -> (HardDrive, SolidStateDrive) """
if isinstance(component, DataStorage):
self['{} {} Size (MB)'.format(type, i)] = component.size
self['{} {} Privacy'.format(type, i)] = component.privacy
# todo decide if is relevant more info about Motherboard
""" Particular fields for component Motherboard """
if isinstance(component, Motherboard):
self['{} {} Slots'.format(type, i)] = component.slots
""" Particular fields for component Processor """
if isinstance(component, Processor):
self['{} {} Number of cores'.format(type, i)] = component.cores
self['{} {} Speed (GHz)'.format(type, i)] = component.speed
""" Particular fields for component RamModule """
if isinstance(component, RamModule):
self['{} {} Size (MB)'.format(type, i)] = component.size
self['{} {} Speed (MHz)'.format(type, i)] = component.speed
self['{} {} Size'.format(type, i)] = component.size
# todo add Display size, ...
# todo add NetworkAdapter speedLink?
# todo add some ComputerAccessories
class ManufacturerView(View): class ManufacturerView(View):

View File

@ -0,0 +1,109 @@
from collections import OrderedDict
from flask import current_app
from ereuse_devicehub.resources.device import models as d
class DeviceRow(OrderedDict):
NUMS = {
d.Display.t: 1,
d.Processor.t: 2,
d.GraphicCard.t: 2,
d.Motherboard.t: 1,
d.NetworkAdapter.t: 2,
d.SoundCard.t: 2
}
def __init__(self, device: d.Device) -> None:
super().__init__()
self.device = device
# General information about device
self['Type'] = device.t
if isinstance(device, d.Computer):
self['Chassis'] = device.chassis
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_event_of()
self['Price'] = device.price
self['Registered in'] = format(device.created, '%c')
if isinstance(device, d.Computer):
self['Processor'] = device.processor_model
self['RAM (GB)'] = device.ram_size
self['Storage Size (MB)'] = device.data_storage_size
rate = device.rate
if rate:
self['Rate'] = rate.rating
self['Range'] = rate.rating_range
self['Processor Rate'] = rate.processor
self['Processor Range'] = rate.workbench.processor_range
self['RAM Rate'] = rate.ram
self['RAM Range'] = rate.workbench.ram_range
self['Data Storage Rate'] = rate.data_storage
self['Data Storage Range'] = rate.workbench.data_storage_range
# More specific information about components
if isinstance(device, d.Computer):
self.components()
def components(self):
"""
Function to get all components information of a device
"""
assert isinstance(self.device, d.Computer)
# todo put an input specific order (non alphabetic)
for type in sorted(current_app.resources[d.Component.t].subresources_types): # type: str
max = self.NUMS.get(type, 4)
if type not in ['Component', 'HardDrive', 'SolidStateDrive']:
i = 1
for component in (r for r in self.device.components if r.type == type):
self.fill_component(type, i, component)
i += 1
if i > max:
break
while i <= max:
self.fill_component(type, i)
i += 1
def fill_component(self, type, i, component=None):
"""
Function to put specific information of components in OrderedDict (csv)
:param type: type of component
:param component: device.components
"""
self['{} {}'.format(type, i)] = format(component) if component else ''
self['{} {} Manufacturer'.format(type, i)] = component.serial_number if component else ''
self['{} {} Model'.format(type, i)] = component.serial_number if component else ''
self['{} {} Serial Number'.format(type, i)] = component.serial_number if component else ''
""" Particular fields for component GraphicCard """
if isinstance(component, d.GraphicCard):
self['{} {} Memory (MB)'.format(type, i)] = component.memory
""" Particular fields for component DataStorage.t -> (HardDrive, SolidStateDrive) """
if isinstance(component, d.DataStorage):
self['{} {} Size (MB)'.format(type, i)] = component.size
self['{} {} Privacy'.format(type, i)] = component.privacy
# todo decide if is relevant more info about Motherboard
""" Particular fields for component Motherboard """
if isinstance(component, d.Motherboard):
self['{} {} Slots'.format(type, i)] = component.slots
""" Particular fields for component Processor """
if isinstance(component, d.Processor):
self['{} {} Number of cores'.format(type, i)] = component.cores
self['{} {} Speed (GHz)'.format(type, i)] = component.speed
""" Particular fields for component RamModule """
if isinstance(component, d.RamModule):
self['{} {} Size (MB)'.format(type, i)] = component.size
self['{} {} Speed (MHz)'.format(type, i)] = component.speed
self['{} {} Size'.format(type, i)] = component.size
# todo add Display size, ...
# todo add NetworkAdapter speedLink?
# todo add some ComputerAccessories

View File

@ -1,5 +1,8 @@
import csv
import datetime
import enum import enum
import uuid import uuid
from io import StringIO
from typing import Callable, Iterable, Tuple from typing import Callable, Iterable, Tuple
import boltons import boltons
@ -7,11 +10,14 @@ 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 teal.cache import cache
from teal.resource import Resource from teal.resource import Resource
from ereuse_devicehub.db import db from ereuse_devicehub.db import db
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.event import models as evs from ereuse_devicehub.resources.event import models as evs
@ -97,6 +103,33 @@ class DocumentView(DeviceView):
return flask.render_template('documents/erasure.html', **params) return flask.render_template('documents/erasure.html', **params)
class DevicesDocumentView(DeviceView):
@cache(datetime.timedelta(minutes=1))
def find(self, args: dict):
query = self.query(args)
return self.generate_post_csv(query)
def generate_post_csv(self, query):
"""
Get device query and put information in csv format
:param query:
:return:
"""
data = StringIO()
cw = csv.writer(data)
first = True
for device in query:
d = DeviceRow(device)
if first:
cw.writerow(name for name in d.keys())
first = False
cw.writerow(v for v in d.values())
output = make_response(data.getvalue())
output.headers['Content-Disposition'] = 'attachment; filename=export.csv'
output.headers['Content-type'] = 'text/csv'
return output
class DocumentDef(Resource): class DocumentDef(Resource):
__type__ = 'Document' __type__ = 'Document'
SCHEMA = None SCHEMA = None
@ -124,3 +157,9 @@ class DocumentDef(Resource):
self.add_url_rule('/erasures/', defaults=d, view_func=view, methods=get) self.add_url_rule('/erasures/', defaults=d, view_func=view, methods=get)
self.add_url_rule('/erasures/<{}:{}>'.format(self.ID_CONVERTER.value, self.ID_NAME), self.add_url_rule('/erasures/<{}:{}>'.format(self.ID_CONVERTER.value, self.ID_NAME),
view_func=view, methods=get) view_func=view, methods=get)
devices_view = DevicesDocumentView.as_view('devicesDocumentView',
definition=self,
auth=app.auth)
if self.AUTH:
devices_view = app.auth.requires_auth(devices_view)
self.add_url_rule('/devices/', defaults=d, view_func=devices_view, methods=get)

View File

@ -28,6 +28,7 @@ def test_api_docs(client: Client):
'/lots/{id}/children', '/lots/{id}/children',
'/lots/{id}/devices', '/lots/{id}/devices',
'/documents/erasures/', '/documents/erasures/',
'/documents/devices/',
'/documents/static/{filename}', '/documents/static/{filename}',
'/tags/{tag_id}/device/{device_id}', '/tags/{tag_id}/device/{device_id}',
'/devices/static/{filename}' '/devices/static/{filename}'

View File

@ -3,8 +3,10 @@ from datetime import datetime
from io import StringIO from io import StringIO
from pathlib import Path from pathlib import Path
import pytest
from ereuse_devicehub.client import UserClient from ereuse_devicehub.client import UserClient
from ereuse_devicehub.resources.device.models import Device from ereuse_devicehub.resources.documents import documents as docs
from ereuse_devicehub.resources.event.models import Snapshot from ereuse_devicehub.resources.event.models import Snapshot
from tests.conftest import file from tests.conftest import file
@ -14,7 +16,8 @@ def test_export_basic_snapshot(user: UserClient):
Test export device information in a csv file Test export device information in a csv file
""" """
snapshot, _ = user.post(file('basic.snapshot'), res=Snapshot) snapshot, _ = user.post(file('basic.snapshot'), res=Snapshot)
csv_str, _ = user.get(res=Device, csv_str, _ = user.get(res=docs.DocumentDef.t,
item='devices/',
accept='text/csv', accept='text/csv',
query=[('filter', {'type': ['Computer']})]) query=[('filter', {'type': ['Computer']})])
f = StringIO(csv_str) f = StringIO(csv_str)
@ -40,7 +43,8 @@ def test_export_full_snapshot(user: UserClient):
Test a export device with all information and a lot of components Test a export device with all information and a lot of components
""" """
snapshot, _ = user.post(file('real-eee-1001pxd.snapshot.11'), res=Snapshot) snapshot, _ = user.post(file('real-eee-1001pxd.snapshot.11'), res=Snapshot)
csv_str, _ = user.get(res=Device, csv_str, _ = user.get(res=docs.DocumentDef.t,
item='devices/',
accept='text/csv', accept='text/csv',
query=[('filter', {'type': ['Computer']})]) query=[('filter', {'type': ['Computer']})])
f = StringIO(csv_str) f = StringIO(csv_str)
@ -67,7 +71,9 @@ def test_export_empty(user: UserClient):
""" """
Test to check works correctly exporting csv without any information (no snapshot) Test to check works correctly exporting csv without any information (no snapshot)
""" """
csv_str, _ = user.get(res=Device, accept='text/csv') csv_str, _ = user.get(res=docs.DocumentDef.t,
accept='text/csv',
item='devices/')
f = StringIO(csv_str) f = StringIO(csv_str)
obj_csv = csv.reader(f, f) obj_csv = csv.reader(f, f)
export_csv = list(obj_csv) export_csv = list(obj_csv)
@ -80,7 +86,8 @@ def test_export_computer_monitor(user: UserClient):
Test a export device type computer monitor Test a export device type computer monitor
""" """
snapshot, _ = user.post(file('computer-monitor.snapshot'), res=Snapshot) snapshot, _ = user.post(file('computer-monitor.snapshot'), res=Snapshot)
csv_str, _ = user.get(res=Device, csv_str, _ = user.get(res=docs.DocumentDef.t,
item='devices/',
accept='text/csv', accept='text/csv',
query=[('filter', {'type': ['ComputerMonitor']})]) query=[('filter', {'type': ['ComputerMonitor']})])
f = StringIO(csv_str) f = StringIO(csv_str)
@ -105,7 +112,8 @@ def test_export_keyboard(user: UserClient):
Test a export device type keyboard Test a export device type keyboard
""" """
snapshot, _ = user.post(file('keyboard.snapshot'), res=Snapshot) snapshot, _ = user.post(file('keyboard.snapshot'), res=Snapshot)
csv_str, _ = user.get(res=Device, csv_str, _ = user.get(res=docs.DocumentDef.t,
item='devices/',
accept='text/csv', accept='text/csv',
query=[('filter', {'type': ['Keyboard']})]) query=[('filter', {'type': ['Keyboard']})])
f = StringIO(csv_str) f = StringIO(csv_str)
@ -124,6 +132,7 @@ def test_export_keyboard(user: UserClient):
assert fixture_csv[1] == export_csv[1], 'Component information are not equal' assert fixture_csv[1] == export_csv[1], 'Component information are not equal'
@pytest.mark.xfail(reason='Develop test')
def test_export_multiple_devices(user: UserClient): def test_export_multiple_devices(user: UserClient):
""" """
Test a export multiple devices with different components and information Test a export multiple devices with different components and information
@ -131,6 +140,7 @@ def test_export_multiple_devices(user: UserClient):
pass pass
@pytest.mark.xfail(reason='Develop test')
def test_export_only_components(user: UserClient): def test_export_only_components(user: UserClient):
""" """
Test a export only components Test a export only components
@ -138,7 +148,8 @@ def test_export_only_components(user: UserClient):
pass pass
def test_export_computers__and_components(user: UserClient): @pytest.mark.xfail(reason='Develop test')
def test_export_computers_and_components(user: UserClient):
""" """
Test a export multiple devices (computers and independent components) Test a export multiple devices (computers and independent components)
""" """