This repository has been archived on 2024-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
devicehub-teal/ereuse_devicehub/resources/documents/documents.py

463 lines
17 KiB
Python
Raw Normal View History

2019-02-28 17:21:24 +00:00
import csv
import enum
import uuid
2021-05-25 10:57:21 +00:00
import time
2021-03-02 10:42:07 +00:00
import datetime
import pathlib
2020-07-28 14:16:17 +00:00
from collections import OrderedDict
2019-02-28 17:21:24 +00:00
from io import StringIO
from typing import Callable, Iterable, Tuple
2021-02-22 20:18:25 +00:00
from decouple import config
import boltons
import flask
import flask_weasyprint
import teal.marshmallow
from boltons import urlutils
2020-12-21 12:40:07 +00:00
from flask import make_response, g, request
2021-02-22 20:18:25 +00:00
from flask import current_app as app
2020-12-21 12:40:07 +00:00
from flask.json import jsonify
2019-02-28 17:21:24 +00:00
from teal.cache import cache
2020-12-21 12:40:07 +00:00
from teal.resource import Resource, View
2021-04-15 19:21:41 +00:00
from ereuse_devicehub import auth
from ereuse_devicehub.db import db
2021-04-13 16:33:49 +00:00
from ereuse_devicehub.resources.enums import SessionType
2021-05-25 10:57:21 +00:00
from ereuse_devicehub.resources.user.models import Session
from ereuse_devicehub.resources.action import models as evs
from ereuse_devicehub.resources.device import models as devs
from ereuse_devicehub.resources.deliverynote.models import Deliverynote
from ereuse_devicehub.resources.device.views import DeviceView
2021-02-22 14:49:13 +00:00
from ereuse_devicehub.resources.documents.device_row import (DeviceRow, StockRow, ActionRow,
InternalStatsRow)
2020-07-28 14:16:17 +00:00
from ereuse_devicehub.resources.lot import LotView
from ereuse_devicehub.resources.lot.models import Lot
2021-02-17 16:38:54 +00:00
from ereuse_devicehub.resources.hash_reports import insert_hash, ReportHash, verify_hash
2021-08-11 11:40:37 +00:00
from ereuse_devicehub.resources.documents.models import RecycleDocument
from ereuse_devicehub.resources.documents.schemas import RecycleDocument as sh_document
class Format(enum.Enum):
HTML = 'HTML'
PDF = 'PDF'
class DocumentView(DeviceView):
class FindArgs(DeviceView.FindArgs):
format = teal.marshmallow.EnumField(Format, missing=None)
def get(self, id):
"""Get a collection of resources or a specific one.
---
parameters:
- name: id
in: path
description: The identifier of the resource.
type: string
required: false
responses:
200:
description: Return the collection or the specific one.
"""
args = self.QUERY_PARSER.parse(self.find_args,
flask.request,
locations=('querystring',))
if id:
# todo we assume we can pass both device id and action id
# for certificates... how is it going to end up being?
try:
id = uuid.UUID(id)
except ValueError:
try:
id = int(id)
except ValueError:
raise teal.marshmallow.ValidationError('Document must be an ID or UUID.')
else:
query = devs.Device.query.filter_by(id=id)
else:
query = evs.Action.query.filter_by(id=id)
else:
flask.current_app.auth.requires_auth(lambda: None)() # todo not nice
query = self.query(args)
type = urlutils.URL(flask.request.url).path_parts[-2]
if type == 'erasures':
template = self.erasure(query)
if args.get('format') == Format.PDF:
res = flask_weasyprint.render_pdf(
flask_weasyprint.HTML(string=template), download_filename='{}.pdf'.format(type)
)
insert_hash(res.data)
else:
res = flask.make_response(template)
return res
2021-07-28 13:59:11 +00:00
@staticmethod
def erasure(query: db.Query):
def erasures():
for model in query:
if isinstance(model, devs.Computer):
for erasure in model.privacy:
yield erasure
elif isinstance(model, devs.DataStorage):
erasure = model.privacy
if erasure:
yield erasure
else:
assert isinstance(model, evs.EraseBasic)
yield model
url_pdf = boltons.urlutils.URL(flask.request.url)
url_pdf.query_params['format'] = 'PDF'
params = {
'title': 'Erasure Certificate',
'erasures': tuple(erasures()),
'url_pdf': url_pdf.to_text()
}
return flask.render_template('documents/erasure.html', **params)
2019-02-28 17:21:24 +00:00
class DevicesDocumentView(DeviceView):
@cache(datetime.timedelta(minutes=1))
def find(self, args: dict):
query = (x for x in self.query(args) if x.owner_id == g.user.id)
2019-02-28 17:21:24 +00:00
return self.generate_post_csv(query)
def generate_post_csv(self, query):
"""Get device query and put information in csv format."""
2019-02-28 17:21:24 +00:00
data = StringIO()
2020-12-21 15:09:30 +00:00
cw = csv.writer(data, delimiter=';', lineterminator="\n", quotechar='"')
2019-02-28 17:21:24 +00:00
first = True
document_ids = self.get_documents_id()
2019-02-28 17:21:24 +00:00
for device in query:
d = DeviceRow(device, document_ids)
2019-02-28 17:21:24 +00:00
if first:
2019-05-08 17:12:05 +00:00
cw.writerow(d.keys())
2019-02-28 17:21:24 +00:00
first = False
2019-05-08 17:12:05 +00:00
cw.writerow(d.values())
2020-12-21 10:34:03 +00:00
bfile = data.getvalue().encode('utf-8')
output = make_response(bfile)
2020-12-21 15:09:30 +00:00
insert_hash(bfile)
2019-02-28 17:21:24 +00:00
output.headers['Content-Disposition'] = 'attachment; filename=export.csv'
output.headers['Content-type'] = 'text/csv'
return output
def get_documents_id(self):
# documentIds = {dev_id: document_id, ...}
deliverys = Deliverynote.query.all()
documentIds = {x.id: d.document_id for d in deliverys for x in d.lot.devices}
return documentIds
class ActionsDocumentView(DeviceView):
@cache(datetime.timedelta(minutes=1))
def find(self, args: dict):
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):
"""Get device query and put information in csv format."""
data = StringIO()
cw = csv.writer(data, delimiter=';', lineterminator="\n", quotechar='"')
first = True
for device in query:
2021-01-13 17:11:41 +00:00
for allocate in device.get_metrics():
d = ActionRow(allocate)
if first:
cw.writerow(d.keys())
first = False
cw.writerow(d.values())
bfile = data.getvalue().encode('utf-8')
output = make_response(bfile)
insert_hash(bfile)
output.headers['Content-Disposition'] = 'attachment; filename=actions_export.csv'
output.headers['Content-type'] = 'text/csv'
return output
2020-07-28 14:16:17 +00:00
class LotsDocumentView(LotView):
def find(self, args: dict):
query = (x for x in self.query(args) if x.owner_id == g.user.id)
2020-08-03 16:25:55 +00:00
return self.generate_lots_csv(query)
2020-07-28 14:16:17 +00:00
def generate_lots_csv(self, query):
"""Get lot query and put information in csv format."""
2020-07-28 14:16:17 +00:00
data = StringIO()
cw = csv.writer(data)
first = True
for lot in query:
l = LotRow(lot)
if first:
cw.writerow(l.keys())
first = False
cw.writerow(l.values())
bfile = data.getvalue().encode('utf-8')
output = make_response(bfile)
insert_hash(bfile)
2020-07-28 14:16:17 +00:00
output.headers['Content-Disposition'] = 'attachment; filename=lots-info.csv'
output.headers['Content-type'] = 'text/csv'
return output
class LotRow(OrderedDict):
def __init__(self, lot: Lot) -> None:
super().__init__()
self.lot = lot
# General information about lot
2020-07-28 14:16:17 +00:00
self['Id'] = lot.id.hex
self['Name'] = lot.name
self['Registered in'] = format(lot.created, '%c')
try:
self['Description'] = lot.description
except:
self['Description'] = ''
2020-07-23 13:56:51 +00:00
class StockDocumentView(DeviceView):
# @cache(datetime.timedelta(minutes=1))
def find(self, args: dict):
query = (x for x in self.query(args) if x.owner_id == g.user.id)
2020-07-23 13:56:51 +00:00
return self.generate_post_csv(query)
def generate_post_csv(self, query):
"""Get device query and put information in csv format."""
data = StringIO()
2021-02-22 14:49:13 +00:00
cw = csv.writer(data, delimiter=';', lineterminator="\n", quotechar='"')
2020-07-23 13:56:51 +00:00
first = True
for device in query:
2020-08-05 09:56:59 +00:00
d = StockRow(device)
2020-07-23 13:56:51 +00:00
if first:
cw.writerow(d.keys())
first = False
cw.writerow(d.values())
bfile = data.getvalue().encode('utf-8')
output = make_response(bfile)
insert_hash(bfile)
output.headers['Content-Disposition'] = 'attachment; filename=devices-stock.csv'
2020-07-23 13:56:51 +00:00
output.headers['Content-type'] = 'text/csv'
return output
2020-12-21 12:40:07 +00:00
class CheckView(View):
model = ReportHash
def get(self):
qry = dict(request.values)
2020-12-21 15:09:30 +00:00
hash3 = qry.get('hash')
2020-12-21 12:40:07 +00:00
result = False
2020-12-21 15:09:30 +00:00
if hash3 and ReportHash.query.filter_by(hash3=hash3).count():
2020-12-21 12:40:07 +00:00
result = True
return jsonify(result)
2021-01-18 11:40:31 +00:00
class StampsView(View):
"""
This view render one public ans static page for see the links for to do the check
of one csv file
"""
2021-02-17 16:38:54 +00:00
def get_url_path(self):
2021-01-18 16:18:47 +00:00
url = urlutils.URL(request.url)
url.normalize()
url.path_parts = url.path_parts[:-2] + ['check', '']
2021-03-16 19:50:57 +00:00
return url.to_text()
2021-02-17 16:38:54 +00:00
def get(self):
result = ('', '')
return flask.render_template('documents/stamp.html', rq_url=self.get_url_path(),
result=result)
def post(self):
result = ('', '')
if 'docUpload' in request.files:
file_check = request.files['docUpload']
2021-03-04 09:38:04 +00:00
bad = 'There are no coincidences. The attached file data does not come \
from our backend or it has been subsequently modified.'
ok = '100% coincidence. The attached file contains data 100% existing in \
to our backend'
result = ('Bad', bad)
2021-08-13 15:16:31 +00:00
mime = ['text/csv', 'application/pdf', 'text/plain','text/markdown',
'image/jpeg', 'image/png', 'text/html',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.oasis.opendocument.spreadsheet',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/msword']
if file_check.mimetype in mime:
2021-02-17 16:38:54 +00:00
if verify_hash(file_check):
2021-03-04 09:38:04 +00:00
result = ('Ok', ok)
2021-02-17 16:38:54 +00:00
return flask.render_template('documents/stamp.html', rq_url=self.get_url_path(),
result=result)
2021-01-18 11:40:31 +00:00
2021-08-10 10:45:03 +00:00
class RecycleDocumentView(View):
"""
This view allow save one document as a proof of one container with some weight was send to recycling.
"""
def post(self):
2021-08-11 11:40:37 +00:00
# import pdb; pdb.set_trace()
2021-08-10 10:45:03 +00:00
data = request.get_data()
schema = sh_document()
doc = schema.loads(data)
document = RecycleDocument(**doc)
db.session.add(document)
db.session().final_flush()
ret = jsonify(document)
ret.status_code = 201
db.session.commit()
return ret
2021-02-22 20:18:25 +00:00
class InternalStatsView(DeviceView):
@cache(datetime.timedelta(minutes=1))
def find(self, args: dict):
if not g.user.email == app.config['EMAIL_ADMIN']:
return jsonify('')
query = evs.Action.query.filter(
evs.Action.type.in_(('Snapshot', 'Live', 'Allocate', 'Deallocate')))
return self.generate_post_csv(query)
2021-02-19 11:53:42 +00:00
2021-02-22 20:18:25 +00:00
def generate_post_csv(self, query):
2021-02-22 14:49:13 +00:00
d = {}
2021-02-22 20:18:25 +00:00
for ac in query:
2021-02-22 14:49:13 +00:00
create = '{}-{}'.format(ac.created.year, ac.created.month)
user = ac.author.email
if not user in d:
2021-02-22 20:18:25 +00:00
d[user] = {}
2021-02-22 14:49:13 +00:00
if not create in d[user]:
d[user][create] = []
d[user][create].append(ac)
data = StringIO()
cw = csv.writer(data, delimiter=';', lineterminator="\n", quotechar='"')
cw.writerow(InternalStatsRow('', "2000-1", []).keys())
for user, createds in d.items():
for create, actions in createds.items():
cw.writerow(InternalStatsRow(user, create, actions).values())
bfile = data.getvalue().encode('utf-8')
output = make_response(bfile)
insert_hash(bfile)
output.headers['Content-Disposition'] = 'attachment; filename=internal-stats.csv'
output.headers['Content-type'] = 'text/csv'
return output
2021-02-19 11:53:42 +00:00
2021-03-02 10:42:07 +00:00
2021-03-02 16:11:34 +00:00
class WbConfDocumentView(DeviceView):
2021-03-01 18:21:57 +00:00
def get(self, wbtype: str):
2021-03-02 10:42:07 +00:00
if not wbtype.lower() in ['usodyrate', 'usodywipe']:
return jsonify('')
2021-04-13 16:33:49 +00:00
data = {'token': self.get_token(),
2021-05-26 09:00:43 +00:00
'host': app.config['HOST'],
'inventory': app.config['SCHEMA']
2021-03-02 10:42:07 +00:00
}
2021-04-12 10:05:52 +00:00
data['erase'] = False
2021-04-12 17:34:33 +00:00
# data['erase'] = True if wbtype == 'usodywipe' else False
2021-03-02 10:42:07 +00:00
2021-04-12 10:05:52 +00:00
env = flask.render_template('documents/wbSettings.ini', **data)
2021-03-02 10:42:07 +00:00
output = make_response(env)
2021-04-12 10:05:52 +00:00
output.headers['Content-Disposition'] = 'attachment; filename=settings.ini'
2021-03-02 10:42:07 +00:00
output.headers['Content-type'] = 'text/plain'
return output
2021-03-01 18:21:57 +00:00
2021-04-13 16:33:49 +00:00
def get_token(self):
2021-05-25 10:57:21 +00:00
if not g.user.sessions:
ses = Session(user=g.user)
db.session.add(ses)
db.session.commit()
tk = ''
now = time.time()
for s in g.user.sessions:
if s.type == SessionType.Internal and (s.expired == 0 or s.expired > now):
tk = s.token
break
assert tk != ''
2021-04-15 19:21:41 +00:00
token = auth.Auth.encode(tk)
return token
2021-04-13 16:33:49 +00:00
2021-02-19 11:53:42 +00:00
class DocumentDef(Resource):
__type__ = 'Document'
SCHEMA = None
VIEW = None # We do not want to create default / documents endpoint
AUTH = False
2020-08-17 14:45:18 +00:00
def __init__(self, app,
import_name=__name__,
static_folder='static',
static_url_path=None,
template_folder='templates',
url_prefix=None,
subdomain=None,
url_defaults=None,
root_path=None,
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
url_prefix, subdomain, url_defaults, root_path, cli_commands)
d = {'id': None}
get = {'GET'}
view = DocumentView.as_view('main', definition=self, auth=app.auth)
# TODO @cayop This two lines never pass
if self.AUTH:
view = app.auth.requires_auth(view)
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),
view_func=view, methods=get)
2019-02-28 17:21:24 +00:00
devices_view = DevicesDocumentView.as_view('devicesDocumentView',
definition=self,
auth=app.auth)
devices_view = app.auth.requires_auth(devices_view)
2020-07-23 18:55:27 +00:00
stock_view = StockDocumentView.as_view('stockDocumentView', definition=self)
2020-08-06 14:51:49 +00:00
stock_view = app.auth.requires_auth(stock_view)
2020-07-23 18:55:27 +00:00
2019-02-28 17:21:24 +00:00
self.add_url_rule('/devices/', defaults=d, view_func=devices_view, methods=get)
lots_view = LotsDocumentView.as_view('lotsDocumentView', definition=self)
lots_view = app.auth.requires_auth(lots_view)
2020-07-28 14:16:17 +00:00
self.add_url_rule('/lots/', defaults=d, view_func=lots_view, methods=get)
stock_view = StockDocumentView.as_view('stockDocumentView', definition=self, auth=app.auth)
stock_view = app.auth.requires_auth(stock_view)
2020-07-23 13:56:51 +00:00
self.add_url_rule('/stock/', defaults=d, view_func=stock_view, methods=get)
2020-12-21 12:40:07 +00:00
check_view = CheckView.as_view('CheckView', definition=self, auth=app.auth)
self.add_url_rule('/check/', defaults={}, view_func=check_view, methods=get)
2021-01-18 11:40:31 +00:00
stamps_view = StampsView.as_view('StampsView', definition=self, auth=app.auth)
2021-02-17 16:38:54 +00:00
self.add_url_rule('/stamps/', defaults={}, view_func=stamps_view, methods={'GET', 'POST'})
2021-01-18 11:40:31 +00:00
2021-02-22 20:18:25 +00:00
internalstats_view = InternalStatsView.as_view(
'InternalStatsView', definition=self, auth=app.auth)
internalstats_view = app.auth.requires_auth(internalstats_view)
self.add_url_rule('/internalstats/', defaults=d, view_func=internalstats_view,
methods=get)
2021-02-19 11:53:42 +00:00
2021-02-22 20:18:25 +00:00
actions_view = ActionsDocumentView.as_view('ActionsDocumentView',
definition=self,
auth=app.auth)
actions_view = app.auth.requires_auth(actions_view)
self.add_url_rule('/actions/', defaults=d, view_func=actions_view, methods=get)
2021-03-01 18:21:57 +00:00
2021-03-02 10:42:07 +00:00
wbconf_view = WbConfDocumentView.as_view('WbConfDocumentView',
2021-03-01 18:21:57 +00:00
definition=self,
auth=app.auth)
wbconf_view = app.auth.requires_auth(wbconf_view)
2021-03-02 10:42:07 +00:00
self.add_url_rule('/wbconf/<string:wbtype>', view_func=wbconf_view, methods=get)
2021-08-10 10:45:03 +00:00
recycle_doc_view = RecycleDocumentView.as_view('RecycleDocumentView', definition=self, auth=app.auth)
self.add_url_rule('/recycle/', defaults={}, view_func=recycle_doc_view, methods={'POST'})