From 252c60ea89a1c5e5abbabf20473f2b67e4546d4a Mon Sep 17 00:00:00 2001 From: yiorgos marinellis Date: Wed, 22 Apr 2020 17:53:06 +0200 Subject: [PATCH 1/9] Filter cumputer devices on current user and lot's deliverynote receiver --- ereuse_devicehub/resources/device/views.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ereuse_devicehub/resources/device/views.py b/ereuse_devicehub/resources/device/views.py index 2a7a5efd..10a8fdff 100644 --- a/ereuse_devicehub/resources/device/views.py +++ b/ereuse_devicehub/resources/device/views.py @@ -1,7 +1,7 @@ import datetime import marshmallow -from flask import current_app as app, render_template, request, Response +from flask import g, current_app as app, render_template, request, Response from flask.json import jsonify from flask_sqlalchemy import Pagination from marshmallow import fields, fields as f, validate as v, ValidationError @@ -19,6 +19,7 @@ from ereuse_devicehub.resources.device.models import Device, Manufacturer, Compu from ereuse_devicehub.resources.device.search import DeviceSearch from ereuse_devicehub.resources.lot.models import LotDeviceDescendants from ereuse_devicehub.resources.tag.model import Tag +from ereuse_devicehub.resources.deliverynote.models import Deliverynote class OfType(f.Str): @@ -60,7 +61,9 @@ class Filters(query.Query): # todo This part of the query is really slow # And forces usage of distinct, as it returns many rows # due to having multiple paths to the same - lot = query.Join(Device.id == LotDeviceDescendants.device_id, LotQ) + lot = query.Join((Device.id == LotDeviceDescendants.device_id) + & (Deliverynote.lot_id == LotDeviceDescendants.ancestor_lot_id), + LotQ) class Sorting(query.Sort): @@ -139,7 +142,11 @@ class DeviceView(View): ) def query(self, args): - query = Device.query.distinct() # todo we should not force to do this if the query is ok + # query = Device.query.distinct() # todo we should not force to do this if the query is ok + query = Device.query.distinct() \ + .filter(Computer.owner_id == g.user.id) \ + .filter(Deliverynote.receiver_address == g.user.email) + search_p = args.get('search', None) if search_p: properties = DeviceSearch.properties From 59d6d69bdad606f66b08f03bdf4d0cfaf041559a Mon Sep 17 00:00:00 2001 From: yiorgos marinellis Date: Tue, 28 Apr 2020 21:20:40 +0200 Subject: [PATCH 2/9] Add user filtering which is evaluate whether or not lot is specified --- ereuse_devicehub/resources/device/views.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/ereuse_devicehub/resources/device/views.py b/ereuse_devicehub/resources/device/views.py index 10a8fdff..f4bfc960 100644 --- a/ereuse_devicehub/resources/device/views.py +++ b/ereuse_devicehub/resources/device/views.py @@ -61,8 +61,7 @@ class Filters(query.Query): # todo This part of the query is really slow # And forces usage of distinct, as it returns many rows # due to having multiple paths to the same - lot = query.Join((Device.id == LotDeviceDescendants.device_id) - & (Deliverynote.lot_id == LotDeviceDescendants.ancestor_lot_id), + lot = query.Join((Device.id == LotDeviceDescendants.device_id), LotQ) @@ -142,10 +141,7 @@ class DeviceView(View): ) def query(self, args): - # query = Device.query.distinct() # todo we should not force to do this if the query is ok - query = Device.query.distinct() \ - .filter(Computer.owner_id == g.user.id) \ - .filter(Deliverynote.receiver_address == g.user.email) + query = Device.query.distinct() # todo we should not force to do this if the query is ok search_p = args.get('search', None) if search_p: @@ -156,9 +152,18 @@ class DeviceView(View): ).order_by( search.Search.rank(properties, search_p) + search.Search.rank(tags, search_p) ) + query = self.user_filter(query) return query.filter(*args['filter']).order_by(*args['sort']) + def user_filter(self, query): + filterqs = request.args.get('filter', None) + if (filterqs and + 'lot' not in filterqs): + query = query.filter((Computer.id == Device.id), (Computer.owner_id == g.user.id)) + pass + return query + class ManufacturerView(View): class FindArgs(marshmallow.Schema): search = marshmallow.fields.Str(required=True, From bd0621884b9c2a97b8edde2f662d9973fe355945 Mon Sep 17 00:00:00 2001 From: yiorgos marinellis Date: Tue, 28 Apr 2020 22:33:33 +0200 Subject: [PATCH 3/9] Filter lots with owner id or deliverynote receiver or supplier --- ereuse_devicehub/resources/device/views.py | 2 +- ereuse_devicehub/resources/lot/views.py | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/ereuse_devicehub/resources/device/views.py b/ereuse_devicehub/resources/device/views.py index f4bfc960..916c5ec6 100644 --- a/ereuse_devicehub/resources/device/views.py +++ b/ereuse_devicehub/resources/device/views.py @@ -156,7 +156,7 @@ class DeviceView(View): return query.filter(*args['filter']).order_by(*args['sort']) - def user_filter(self, query): + def visibility_filter(self, query): filterqs = request.args.get('filter', None) if (filterqs and 'lot' not in filterqs): diff --git a/ereuse_devicehub/resources/lot/views.py b/ereuse_devicehub/resources/lot/views.py index 3590ba49..b0741e32 100644 --- a/ereuse_devicehub/resources/lot/views.py +++ b/ereuse_devicehub/resources/lot/views.py @@ -6,16 +6,19 @@ from typing import Dict, List, Set, Union import marshmallow as ma import teal.cache -from flask import Response, jsonify, request +from flask import Response, jsonify, request, g from marshmallow import Schema as MarshmallowSchema, fields as f from teal.marshmallow import EnumField from teal.resource import View +from sqlalchemy import or_ from sqlalchemy.orm import joinedload +from ereuse_devicehub import auth from ereuse_devicehub.db import db from ereuse_devicehub.query import things_response from ereuse_devicehub.resources.device.models import Device, Computer from ereuse_devicehub.resources.lot.models import Lot, Path +from ereuse_devicehub.resources.deliverynote.models import Deliverynote class LotFormat(Enum): @@ -85,6 +88,7 @@ class LotView(View): } else: query = Lot.query + query = self.visibility_filter(query) if args['search']: query = query.filter(Lot.name.ilike(args['search'] + '%')) lots = query.paginate(per_page=6 if args['search'] else 30) @@ -94,6 +98,13 @@ class LotView(View): ) return jsonify(ret) + def visibility_filter(self, query): + query = query.outerjoin(Deliverynote) \ + .filter(or_(Deliverynote.receiver_address == g.user.email, + Deliverynote.supplier_email == g.user.email, + Lot.owner_id == g.user.id)) + return query + def delete(self, id): lot = Lot.query.filter_by(id=id).one() lot.delete() From 2851d4112fc7929cc3737e43bbda71ae9b24a8dc Mon Sep 17 00:00:00 2001 From: yiorgos marinellis Date: Tue, 28 Apr 2020 22:55:17 +0200 Subject: [PATCH 4/9] Fix typo --- ereuse_devicehub/resources/device/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ereuse_devicehub/resources/device/views.py b/ereuse_devicehub/resources/device/views.py index 916c5ec6..2fc5b52b 100644 --- a/ereuse_devicehub/resources/device/views.py +++ b/ereuse_devicehub/resources/device/views.py @@ -152,7 +152,7 @@ class DeviceView(View): ).order_by( search.Search.rank(properties, search_p) + search.Search.rank(tags, search_p) ) - query = self.user_filter(query) + query = self.visibility_filter(query) return query.filter(*args['filter']).order_by(*args['sort']) From e021702e0c6bdcd7e2eeac61cdece1fab786594e Mon Sep 17 00:00:00 2001 From: yiorgos marinellis Date: Wed, 29 Apr 2020 20:41:32 +0200 Subject: [PATCH 5/9] Update nesting level to depth 2 when returning lots not UiTree --- ereuse_devicehub/resources/lot/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ereuse_devicehub/resources/lot/views.py b/ereuse_devicehub/resources/lot/views.py index b0741e32..97fcb064 100644 --- a/ereuse_devicehub/resources/lot/views.py +++ b/ereuse_devicehub/resources/lot/views.py @@ -93,7 +93,7 @@ class LotView(View): query = query.filter(Lot.name.ilike(args['search'] + '%')) lots = query.paginate(per_page=6 if args['search'] else 30) return things_response( - self.schema.dump(lots.items, many=True, nested=0), + self.schema.dump(lots.items, many=True, nested=2), lots.page, lots.per_page, lots.total, lots.prev_num, lots.next_num ) return jsonify(ret) From cdf36583a9b19285f36c9df2974db19acedba281 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 5 Aug 2020 17:51:13 +0200 Subject: [PATCH 6/9] bug fixed: there is no point in having self.AUTH for view DocumentDef --- ereuse_devicehub/resources/documents/documents.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ereuse_devicehub/resources/documents/documents.py b/ereuse_devicehub/resources/documents/documents.py index 3b148362..da7795f1 100644 --- a/ereuse_devicehub/resources/documents/documents.py +++ b/ereuse_devicehub/resources/documents/documents.py @@ -153,7 +153,6 @@ class DocumentDef(Resource): __type__ = 'Document' SCHEMA = None VIEW = None # We do not want to create default / documents endpoint - AUTH = False def __init__(self, app, import_name=__name__, @@ -171,18 +170,17 @@ class DocumentDef(Resource): get = {'GET'} view = DocumentView.as_view('main', definition=self, auth=app.auth) - if self.AUTH: - view = app.auth.requires_auth(view) + + 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) 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) - if self.AUTH: - devices_view = app.auth.requires_auth(devices_view) self.add_url_rule('/devices/', defaults=d, view_func=devices_view, methods=get) self.add_url_rule('/stock/', defaults=d, view_func=stock_view, methods=get) From 27ab4cf2efa3abcb0b6c27cb6d9307be93813ea0 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 5 Aug 2020 18:19:27 +0200 Subject: [PATCH 7/9] user instead of client --- tests/test_documents.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/test_documents.py b/tests/test_documents.py index 0b40bf23..e5ae5b9d 100644 --- a/tests/test_documents.py +++ b/tests/test_documents.py @@ -13,19 +13,19 @@ from tests.conftest import file @pytest.mark.mvp -def test_erasure_certificate_public_one(user: UserClient, client: Client): +def test_erasure_certificate_public_one(user: UserClient): """Public user can get certificate from one device as HTML or PDF.""" s = file('erase-sectors.snapshot') snapshot, _ = user.post(s, res=Snapshot) - doc, response = client.get(res=documents.DocumentDef.t, + doc, response = user.get(res=documents.DocumentDef.t, item='erasures/{}'.format(snapshot['device']['id']), accept=ANY) assert 'html' in response.content_type assert ' Date: Thu, 6 Aug 2020 15:55:14 +0200 Subject: [PATCH 8/9] return to put anonymous access to erasures documents --- .../resources/documents/documents.py | 16 +++++++++++----- tests/test_documents.py | 10 +++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/ereuse_devicehub/resources/documents/documents.py b/ereuse_devicehub/resources/documents/documents.py index da7795f1..baa33ecc 100644 --- a/ereuse_devicehub/resources/documents/documents.py +++ b/ereuse_devicehub/resources/documents/documents.py @@ -20,6 +20,7 @@ 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 flask import g, request class Format(enum.Enum): HTML = 'HTML' @@ -153,7 +154,7 @@ class DocumentDef(Resource): __type__ = 'Document' SCHEMA = None VIEW = None # We do not want to create default / documents endpoint - + AUTH = False def __init__(self, app, import_name=__name__, static_folder='static', @@ -171,16 +172,21 @@ class DocumentDef(Resource): view = DocumentView.as_view('main', definition=self, auth=app.auth) - view = app.auth.requires_auth(view) + # 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) + 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) - 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 = app.auth.requires_auth(stock_view) self.add_url_rule('/stock/', defaults=d, view_func=stock_view, methods=get) diff --git a/tests/test_documents.py b/tests/test_documents.py index e5ae5b9d..0c478930 100644 --- a/tests/test_documents.py +++ b/tests/test_documents.py @@ -13,7 +13,7 @@ from tests.conftest import file @pytest.mark.mvp -def test_erasure_certificate_public_one(user: UserClient): +def test_erasure_certificate_public_one(user: UserClient, client: Client): """Public user can get certificate from one device as HTML or PDF.""" s = file('erase-sectors.snapshot') snapshot, _ = user.post(s, res=Snapshot) @@ -25,7 +25,7 @@ def test_erasure_certificate_public_one(user: UserClient): assert ' Date: Wed, 12 Aug 2020 11:59:12 +0200 Subject: [PATCH 9/9] Deselect export tests for mvp --- tests/test_documents.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_documents.py b/tests/test_documents.py index 0c478930..81a83bd8 100644 --- a/tests/test_documents.py +++ b/tests/test_documents.py @@ -145,7 +145,7 @@ def test_export_empty(user: UserClient): assert len(export_csv) == 0, 'Csv is not empty' -@pytest.mark.mvp +@pytest.mark.xfail(reason='Feature not developed (Beta)') def test_export_computer_monitor(user: UserClient): """Test a export device type computer monitor.""" snapshot, _ = user.post(file('computer-monitor.snapshot'), res=Snapshot) @@ -170,6 +170,7 @@ def test_export_computer_monitor(user: UserClient): assert fixture_csv[1] == export_csv[1], 'Component information are not equal' +@pytest.mark.xfail(reason='Feature not developed (Beta)') def test_export_keyboard(user: UserClient): """Test a export device type keyboard.""" snapshot, _ = user.post(file('keyboard.snapshot'), res=Snapshot) @@ -193,7 +194,7 @@ def test_export_keyboard(user: UserClient): assert fixture_csv[1] == export_csv[1], 'Component information are not equal' -@pytest.mark.mvp +@pytest.mark.xfail(reason='Feature not developed (Beta)') def test_export_multiple_different_devices(user: UserClient): """Test function 'Export' of multiple different device types (like computers, keyboards, monitors, etc..)