From b195e380a9700978843545fbcfefb9ea384b7ef4 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 22 Sep 2021 14:01:49 +0200 Subject: [PATCH 01/17] adding basic action reuse and recycling --- CHANGELOG.md | 1 + ereuse_devicehub/__init__.py | 2 +- ereuse_devicehub/resources/action/__init__.py | 10 ++++++++++ ereuse_devicehub/resources/action/models.py | 8 ++++++++ ereuse_devicehub/resources/action/schemas.py | 8 ++++++++ 5 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 298f5c8a..b5709f91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ml). ## testing [1.0.10-beta] +- [addend] #166 new action recycling and reuse ## [1.0.10-beta] diff --git a/ereuse_devicehub/__init__.py b/ereuse_devicehub/__init__.py index d44f1a11..7a5be26c 100644 --- a/ereuse_devicehub/__init__.py +++ b/ereuse_devicehub/__init__.py @@ -1 +1 @@ -__version__ = "1.0.9-beta" +__version__ = "1.0.10-beta" diff --git a/ereuse_devicehub/resources/action/__init__.py b/ereuse_devicehub/resources/action/__init__.py index bdc55014..341d5640 100644 --- a/ereuse_devicehub/resources/action/__init__.py +++ b/ereuse_devicehub/resources/action/__init__.py @@ -194,6 +194,16 @@ class ReadyDef(ActionDef): SCHEMA = schemas.Ready +class RecyclingDef(ActionDef): + VIEW = None + SCHEMA = schemas.Recycling + + +class ReuseDef(ActionDef): + VIEW = None + SCHEMA = schemas.Reuse + + class ToPrepareDef(ActionDef): VIEW = None SCHEMA = schemas.ToPrepare diff --git a/ereuse_devicehub/resources/action/models.py b/ereuse_devicehub/resources/action/models.py index fdbdf1fa..7529b513 100644 --- a/ereuse_devicehub/resources/action/models.py +++ b/ereuse_devicehub/resources/action/models.py @@ -1341,6 +1341,14 @@ class DataWipe(JoinedTableMixin, ActionWithMultipleDevices): primaryjoin='DataWipe.document_id == DataWipeDocument.id') +class Recycling(ActionWithMultipleDevices): + """This action mark one devices or container as recycled""" + + +class Reuse(ActionWithMultipleDevices): + """This action mark one devices or container as reuse""" + + class Prepare(ActionWithMultipleDevices): """Work has been performed to the device to a defined point of acceptance. diff --git a/ereuse_devicehub/resources/action/schemas.py b/ereuse_devicehub/resources/action/schemas.py index 3232f11b..22ff595c 100644 --- a/ereuse_devicehub/resources/action/schemas.py +++ b/ereuse_devicehub/resources/action/schemas.py @@ -423,6 +423,14 @@ class Ready(ActionWithMultipleDevices): __doc__ = m.Ready.__doc__ +class Recycling(ActionWithMultipleDevices): + __doc__ = m.Recycling.__doc__ + + +class Reuse(ActionWithMultipleDevices): + __doc__ = m.Reuse.__doc__ + + class ToPrepare(ActionWithMultipleDevices): __doc__ = m.ToPrepare.__doc__ From e861ae3bf8f0ba14c09a4640504ecfcee9ac8ffb Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 22 Sep 2021 14:33:46 +0200 Subject: [PATCH 02/17] adding tests for recycling and reuse --- tests/test_action.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_action.py b/tests/test_action.py index 03868c3c..dc5bb7bb 100644 --- a/tests/test_action.py +++ b/tests/test_action.py @@ -255,6 +255,24 @@ def test_generic_action(action_model_state: Tuple[models.Action, states.Trading] assert snapshot['device']['updated'] != device['updated'] +@pytest.mark.mvp +def test_recycling(user: UserClient): + snap, _ = user.post(file('basic.snapshot'), res=models.Snapshot) + action = {'type': models.Recycling.t, 'devices': [snap['device']['id']]} + action, _ = user.post(action, res=models.Action) + device, _ = user.get(res=Device, item=snap['device']['devicehubID']) + assert device['actions'][-1]['id'] == action['id'] + + +@pytest.mark.mvp +def test_reuse(user: UserClient): + snap, _ = user.post(file('basic.snapshot'), res=models.Snapshot) + action = {'type': models.Reuse.t, 'devices': [snap['device']['id']]} + action, _ = user.post(action, res=models.Action) + device, _ = user.get(res=Device, item=snap['device']['devicehubID']) + assert device['actions'][-1]['id'] == action['id'] + + @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_live(user: UserClient, client: Client, app: Devicehub): From c2b0e6341fbf376bd85807fe574a0bfde5170bb4 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 24 Sep 2021 12:37:33 +0200 Subject: [PATCH 03/17] adding new models for Use, Recycling, Refurbish and Management actions --- .../a0978ac6cf4a_adding_state_actions.py | 38 +++++++++++++++++++ ereuse_devicehub/resources/action/__init__.py | 14 ++++++- ereuse_devicehub/resources/action/models.py | 27 +++++++++++-- ereuse_devicehub/resources/action/schemas.py | 18 +++++++-- 4 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 ereuse_devicehub/migrations/versions/a0978ac6cf4a_adding_state_actions.py diff --git a/ereuse_devicehub/migrations/versions/a0978ac6cf4a_adding_state_actions.py b/ereuse_devicehub/migrations/versions/a0978ac6cf4a_adding_state_actions.py new file mode 100644 index 00000000..02422946 --- /dev/null +++ b/ereuse_devicehub/migrations/versions/a0978ac6cf4a_adding_state_actions.py @@ -0,0 +1,38 @@ +"""adding state actions + +Revision ID: a0978ac6cf4a +Revises: 7ecb8ff7abad +Create Date: 2021-09-24 12:03:01.661679 + +""" +from alembic import op, context +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + + +# revision identifiers, used by Alembic. +revision = 'a0978ac6cf4a' +down_revision = '7ecb8ff7abad' +branch_labels = None +depends_on = None + + +def get_inv(): + INV = context.get_x_argument(as_dictionary=True).get('inventory') + if not INV: + raise ValueError("Inventory value is not specified") + return INV + +def upgrade(): + op.create_table('action_status', + sa.Column('rol_user_id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.ForeignKeyConstraint(['id'], [f'{get_inv()}.action.id'], ), + sa.ForeignKeyConstraint(['rol_user_id'], ['common.user.id'], ), + sa.PrimaryKeyConstraint('id'), + schema=f'{get_inv()}' + ) + + +def downgrade(): + op.drop_table('action_status', schema=f'{get_inv()}') diff --git a/ereuse_devicehub/resources/action/__init__.py b/ereuse_devicehub/resources/action/__init__.py index 341d5640..09e27245 100644 --- a/ereuse_devicehub/resources/action/__init__.py +++ b/ereuse_devicehub/resources/action/__init__.py @@ -199,9 +199,19 @@ class RecyclingDef(ActionDef): SCHEMA = schemas.Recycling -class ReuseDef(ActionDef): +class UseDef(ActionDef): VIEW = None - SCHEMA = schemas.Reuse + SCHEMA = schemas.Use + + +class RefurbishDef(ActionDef): + VIEW = None + SCHEMA = schemas.Refurbish + + +class ManagementDef(ActionDef): + VIEW = None + SCHEMA = schemas.Management class ToPrepareDef(ActionDef): diff --git a/ereuse_devicehub/resources/action/models.py b/ereuse_devicehub/resources/action/models.py index 7529b513..f0722803 100644 --- a/ereuse_devicehub/resources/action/models.py +++ b/ereuse_devicehub/resources/action/models.py @@ -1341,12 +1341,31 @@ class DataWipe(JoinedTableMixin, ActionWithMultipleDevices): primaryjoin='DataWipe.document_id == DataWipeDocument.id') -class Recycling(ActionWithMultipleDevices): - """This action mark one devices or container as recycled""" +class ActionStatus(JoinedTableMixin, ActionWithMultipleDevices): + """This is a meta-action than mark the status of the devices""" + + rol_user_id = db.Column(UUID(as_uuid=True), + db.ForeignKey(User.id), + nullable=False, + default=lambda: g.user.id) + rol_user = db.relationship(User, primaryjoin=rol_user_id == User.id) + rol_user_comment = """The user that .""" -class Reuse(ActionWithMultipleDevices): - """This action mark one devices or container as reuse""" +class Recycling(ActionStatus): + """This action mark one devices or container as recycling""" + + +class Use(ActionStatus): + """This action mark one devices or container as use""" + + +class Refurbish(ActionStatus): + """This action mark one devices or container as refurbish""" + + +class Management(ActionStatus): + """This action mark one devices or container as management""" class Prepare(ActionWithMultipleDevices): diff --git a/ereuse_devicehub/resources/action/schemas.py b/ereuse_devicehub/resources/action/schemas.py index 22ff595c..6f18de75 100644 --- a/ereuse_devicehub/resources/action/schemas.py +++ b/ereuse_devicehub/resources/action/schemas.py @@ -423,12 +423,24 @@ class Ready(ActionWithMultipleDevices): __doc__ = m.Ready.__doc__ -class Recycling(ActionWithMultipleDevices): +class ActionStatus(ActionWithMultipleDevices): + rol_user = NestedOn(s_user.User, dump_only=True, exclude=('token',)) + + +class Recycling(ActionStatus): __doc__ = m.Recycling.__doc__ -class Reuse(ActionWithMultipleDevices): - __doc__ = m.Reuse.__doc__ +class Use(ActionStatus): + __doc__ = m.Use.__doc__ + + +class Refurbish(ActionStatus): + __doc__ = m.Refurbish.__doc__ + + +class Management(ActionStatus): + __doc__ = m.Management.__doc__ class ToPrepare(ActionWithMultipleDevices): From 38db59b150d16e8619d0b1d034c051127e07cefa Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Mon, 27 Sep 2021 14:58:25 +0200 Subject: [PATCH 04/17] put the correct user un rol_user --- ereuse_devicehub/resources/action/schemas.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ereuse_devicehub/resources/action/schemas.py b/ereuse_devicehub/resources/action/schemas.py index 6f18de75..02065081 100644 --- a/ereuse_devicehub/resources/action/schemas.py +++ b/ereuse_devicehub/resources/action/schemas.py @@ -2,7 +2,7 @@ import copy from datetime import datetime, timedelta from dateutil.tz import tzutc from flask import current_app as app, g -from marshmallow import Schema as MarshmallowSchema, ValidationError, fields as f, validates_schema +from marshmallow import Schema as MarshmallowSchema, ValidationError, fields as f, validates_schema, post_load from marshmallow.fields import Boolean, DateTime, Decimal, Float, Integer, Nested, String, \ TimeDelta, UUID from marshmallow.validate import Length, OneOf, Range @@ -426,6 +426,15 @@ class Ready(ActionWithMultipleDevices): class ActionStatus(ActionWithMultipleDevices): rol_user = NestedOn(s_user.User, dump_only=True, exclude=('token',)) + @post_load + def put_rol_user(self, data: dict): + for dev in data['devices']: + if dev.trading in [None, 'Revoke', 'ConfirmRevoke']: + return data + trade = [ac for ac in dev.actions if ac.t == 'Trade'][-1] + if trade.user_to != g.user: + data['rol_user'] = trade.user_to + class Recycling(ActionStatus): __doc__ = m.Recycling.__doc__ From 4ce316a9acf9ada273cea5ed22e48d934a3ba18c Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 28 Sep 2021 11:25:57 +0200 Subject: [PATCH 05/17] fixing tests status actions --- tests/test_action.py | 110 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 2 deletions(-) diff --git a/tests/test_action.py b/tests/test_action.py index dc5bb7bb..99ee0935 100644 --- a/tests/test_action.py +++ b/tests/test_action.py @@ -256,12 +256,118 @@ def test_generic_action(action_model_state: Tuple[models.Action, states.Trading] @pytest.mark.mvp -def test_recycling(user: UserClient): +@pytest.mark.parametrize('action_model', + (pytest.param(ams, id=ams.__class__.__name__) + for ams in [ + models.Recycling, + models.Use, + models.Refurbish, + models.Management + ])) +def test_simple_status_actions(action_model: models.Action, user: UserClient, user2: UserClient): + """Simple test of status action.""" snap, _ = user.post(file('basic.snapshot'), res=models.Snapshot) - action = {'type': models.Recycling.t, 'devices': [snap['device']['id']]} + + action = {'type': action_model.t, 'devices': [snap['device']['id']]} action, _ = user.post(action, res=models.Action) device, _ = user.get(res=Device, item=snap['device']['devicehubID']) assert device['actions'][-1]['id'] == action['id'] + assert action['author']['id'] == user.user['id'] + assert action['rol_user']['id'] == user.user['id'] + + +@pytest.mark.mvp +@pytest.mark.parametrize('action_model', + (pytest.param(ams, id=ams.__class__.__name__) + for ams in [ + models.Recycling, + models.Use, + models.Refurbish, + models.Management + ])) +def test_outgoinlot_status_actions(action_model: models.Action, user: UserClient, user2: UserClient): + """Test of status actions in outgoinlot.""" + snap, _ = user.post(file('basic.snapshot'), res=models.Snapshot) + device, _ = user.get(res=Device, item=snap['device']['devicehubID']) + lot, _ = user.post({'name': 'MyLot'}, res=Lot) + user.post({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=[('id', device['id'])]) + + request_post = { + 'type': 'Trade', + 'devices': [device['id']], + 'userFromEmail': user.email, + 'userToEmail': user2.email, + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'lot': lot['id'], + 'confirms': True, + } + + user.post(res=models.Action, data=request_post) + action = {'type': action_model.t, 'devices': [device['id']]} + action, _ = user.post(action, res=models.Action) + device, _ = user.get(res=Device, item=snap['device']['devicehubID']) + + assert device['actions'][-1]['id'] == action['id'] + assert action['author']['id'] == user.user['id'] + assert action['rol_user']['id'] == user2.user['id'] + + # Remove device from lot + lot, _ = user.delete({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=[('id', device['id'])], status=200) + + action = {'type': action_model.t, 'devices': [device['id']]} + action, _ = user.post(action, res=models.Action) + device, _ = user.get(res=Device, item=snap['device']['devicehubID']) + + assert device['actions'][-1]['id'] == action['id'] + assert action['author']['id'] == user.user['id'] + assert action['rol_user']['id'] == user.user['id'] + + +@pytest.mark.mvp +@pytest.mark.parametrize('action_model', + (pytest.param(ams, id=ams.__class__.__name__) + for ams in [ + models.Recycling, + models.Use, + models.Refurbish, + models.Management + ])) +def test_incominglot_status_actions(action_model: models.Action, user: UserClient, user2: UserClient): + """Test of status actions in outgoinlot.""" + snap, _ = user.post(file('basic.snapshot'), res=models.Snapshot) + device, _ = user.get(res=Device, item=snap['device']['devicehubID']) + lot, _ = user.post({'name': 'MyLot'}, res=Lot) + user.post({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=[('id', device['id'])]) + + request_post = { + 'type': 'Trade', + 'devices': [device['id']], + 'userFromEmail': user2.email, + 'userToEmail': user.email, + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'lot': lot['id'], + 'confirms': True, + } + + user.post(res=models.Action, data=request_post) + action = {'type': action_model.t, 'devices': [device['id']]} + action, _ = user.post(action, res=models.Action) + device, _ = user.get(res=Device, item=snap['device']['devicehubID']) + + assert device['actions'][-1]['id'] == action['id'] + assert action['author']['id'] == user.user['id'] + assert action['rol_user']['id'] == user.user['id'] @pytest.mark.mvp From f10f2b848fc09f7852d7dabacc3a3ab74ded8c33 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 28 Sep 2021 13:05:58 +0200 Subject: [PATCH 06/17] get the status and history status of one devices --- ereuse_devicehub/resources/device/models.py | 37 +++++++++++++++++++++ ereuse_devicehub/resources/device/states.py | 13 ++++++++ 2 files changed, 50 insertions(+) diff --git a/ereuse_devicehub/resources/device/models.py b/ereuse_devicehub/resources/device/models.py index a0917bef..fc6c3cc3 100644 --- a/ereuse_devicehub/resources/device/models.py +++ b/ereuse_devicehub/resources/device/models.py @@ -261,6 +261,43 @@ class Device(Thing): with suppress(LookupError, ValueError): return self.last_action_of(*states.Trading.actions()) + @property + def status(self): + """Show the actual status of device for this owner. + The status depend of one of this 4 actions: + - Use + - Refurbish + - Recycling + - Management + """ + from ereuse_devicehub.resources.device import states + with suppress(LookupError, ValueError): + return self.last_action_of(*states.Status.actions()) + + @property + def history_status(self): + """Show the history of the status actions of the device. + The status depend of one of this 4 actions: + - Use + - Refurbish + - Recycling + - Management + """ + from ereuse_devicehub.resources.device import states + status_actions = [ac.t for ac in states.Status.actions()] + history = [] + for ac in self.actions: + if not ac.t in status_actions: + continue + if not history: + history.append(ac) + continue + if ac.rol_user == history[-1].rol_user: + # get only the last action consecutive for the same user + history = history[:-1] + [ac] + + return history + @property def trading(self): """The trading state, or None if no Trade action has diff --git a/ereuse_devicehub/resources/device/states.py b/ereuse_devicehub/resources/device/states.py index f6ad1761..5177f701 100644 --- a/ereuse_devicehub/resources/device/states.py +++ b/ereuse_devicehub/resources/device/states.py @@ -83,3 +83,16 @@ class Usage(State): Allocate = e.Allocate Deallocate = e.Deallocate InUse = e.Live + + +class Status(State): + """Define status of device for one user. + :cvar Use: The device is in use for one final user. + :cvar Refurbish: The device is owned by one refurbisher. + :cvar Recycling: The device is sended to recycling. + :cvar Management: The device is owned by one Manager. + """ + Use = e.Use + Refurbish = e.Refurbish + Recycling = e.Recycling + Management = e.Management From 84da94430eeb3f25f2e961438ea13299c665a817 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 28 Sep 2021 15:44:25 +0200 Subject: [PATCH 07/17] fixing bug --- ereuse_devicehub/resources/device/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ereuse_devicehub/resources/device/models.py b/ereuse_devicehub/resources/device/models.py index fc6c3cc3..6fce9e89 100644 --- a/ereuse_devicehub/resources/device/models.py +++ b/ereuse_devicehub/resources/device/models.py @@ -295,6 +295,9 @@ class Device(Thing): if ac.rol_user == history[-1].rol_user: # get only the last action consecutive for the same user history = history[:-1] + [ac] + continue + + history.append(ac) return history From 88780d4949b1599c8f3aba25a7fc8ba9462953c1 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Tue, 28 Sep 2021 15:44:56 +0200 Subject: [PATCH 08/17] adding test for history and status calls --- tests/test_action.py | 49 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/test_action.py b/tests/test_action.py index 99ee0935..2c441771 100644 --- a/tests/test_action.py +++ b/tests/test_action.py @@ -370,6 +370,55 @@ def test_incominglot_status_actions(action_model: models.Action, user: UserClien assert action['rol_user']['id'] == user.user['id'] +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_history_status_actions(user: UserClient, user2: UserClient): + """Test for check the status actions.""" + snap, _ = user.post(file('basic.snapshot'), res=models.Snapshot) + device = Device.query.filter_by(id=snap['device']['id']).one() + + # Case 1 + action = {'type': models.Recycling.t, 'devices': [device.id]} + action, _ = user.post(action, res=models.Action) + + assert str(device.actions[-1].id) == action['id'] + assert action['id'] == str(device.status.id) + assert device.status.t == models.Recycling.t + assert [action['id']] == [str(ac.id) for ac in device.history_status] + + # Case 2 + action2 = {'type': models.Refurbish.t, 'devices': [device.id]} + action2, _ = user.post(action2, res=models.Action) + assert action2['id'] == str(device.status.id) + assert device.status.t == models.Refurbish.t + assert [action2['id']] == [str(ac.id) for ac in device.history_status] + + # Case 3 + lot, _ = user.post({'name': 'MyLot'}, res=Lot) + user.post({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=[('id', device.id)]) + + request_post = { + 'type': 'Trade', + 'devices': [device.id], + 'userFromEmail': user.email, + 'userToEmail': user2.email, + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'lot': lot['id'], + 'confirms': True, + } + + user.post(res=models.Action, data=request_post) + action3 = {'type': models.Use.t, 'devices': [device.id]} + action3, _ = user.post(action3, res=models.Action) + assert action3['id'] == str(device.status.id) + assert device.status.t == models.Use.t + assert [action2['id'], action3['id']] == [str(ac.id) for ac in device.history_status] + + @pytest.mark.mvp def test_reuse(user: UserClient): snap, _ = user.post(file('basic.snapshot'), res=models.Snapshot) From 2828c0755a0237a1c42e4c7e8343eed20a8fffb8 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 29 Sep 2021 11:04:56 +0200 Subject: [PATCH 09/17] adding test for check if you are not the owner --- tests/test_action.py | 50 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/test_action.py b/tests/test_action.py index 2c441771..7cd3e09d 100644 --- a/tests/test_action.py +++ b/tests/test_action.py @@ -419,6 +419,56 @@ def test_history_status_actions(user: UserClient, user2: UserClient): assert [action2['id'], action3['id']] == [str(ac.id) for ac in device.history_status] +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_use_changing_owner(user: UserClient, user2: UserClient): + """Check if is it possible to do a use action for one device + when you are not the owner. + """ + snap, _ = user.post(file('basic.snapshot'), res=models.Snapshot) + device = Device.query.filter_by(id=snap['device']['id']).one() + + assert device.owner.email == user.email + + # Trade + lot, _ = user.post({'name': 'MyLot'}, res=Lot) + user.post({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=[('id', device.id)]) + + request_post = { + 'type': 'Trade', + 'devices': [device.id], + 'userFromEmail': user.email, + 'userToEmail': user2.email, + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'lot': lot['id'], + 'confirms': True, + } + + user.post(res=models.Action, data=request_post) + trade = models.Trade.query.one() + + # Doble confirmation and change of owner + request_confirm = { + 'type': 'Confirm', + 'action': trade.id, + 'devices': [device.id] + } + + user2.post(res=models.Action, data=request_confirm) + assert device.owner.email == user2.email + + # Adding action Use + action3 = {'type': models.Use.t, 'devices': [device.id]} + action3, _ = user.post(action3, res=models.Action) + assert action3['id'] == str(device.status.id) + assert device.status.t == models.Use.t + assert device.owner.email == user2.email + + @pytest.mark.mvp def test_reuse(user: UserClient): snap, _ = user.post(file('basic.snapshot'), res=models.Snapshot) From aff86a8f941087903df278eabeb6010cb58ec856 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 29 Sep 2021 12:59:44 +0200 Subject: [PATCH 10/17] using ActionWithMultipleTradeDocuments intead of ActionWithMultipleDevices --- ereuse_devicehub/resources/action/models.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/ereuse_devicehub/resources/action/models.py b/ereuse_devicehub/resources/action/models.py index f0722803..23ddc0bf 100644 --- a/ereuse_devicehub/resources/action/models.py +++ b/ereuse_devicehub/resources/action/models.py @@ -1341,7 +1341,8 @@ class DataWipe(JoinedTableMixin, ActionWithMultipleDevices): primaryjoin='DataWipe.document_id == DataWipeDocument.id') -class ActionStatus(JoinedTableMixin, ActionWithMultipleDevices): +class ActionStatus(JoinedTableMixin, ActionWithMultipleTradeDocuments): +# class ActionStatus(JoinedTableMixin, ActionWithMultipleDevices): """This is a meta-action than mark the status of the devices""" rol_user_id = db.Column(UUID(as_uuid=True), @@ -1353,7 +1354,7 @@ class ActionStatus(JoinedTableMixin, ActionWithMultipleDevices): class Recycling(ActionStatus): - """This action mark one devices or container as recycling""" + """This action mark devices as recycling""" class Use(ActionStatus): @@ -1489,6 +1490,20 @@ class CancelReservation(Organize): """The act of cancelling a reservation.""" +class ActionStatusDocuments(JoinedTableMixin, ActionWithMultipleTradeDocuments): + """This is a meta-action than mark the status of the devices""" + rol_user_id = db.Column(UUID(as_uuid=True), + db.ForeignKey(User.id), + nullable=False, + default=lambda: g.user.id) + rol_user = db.relationship(User, primaryjoin=rol_user_id == User.id) + rol_user_comment = """The user that .""" + + +class RecyclingDocument(ActionStatusDocuments): + """This action mark one document or container as recycling""" + + class ConfirmDocument(JoinedTableMixin, ActionWithMultipleTradeDocuments): """Users confirm the one action trade this confirmation it's link to trade and the document that confirm From eeb6db7bc2a08a82408a2d832a715df0012d2a58 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 29 Sep 2021 21:12:46 +0200 Subject: [PATCH 11/17] test for recycling documents --- ereuse_devicehub/resources/action/schemas.py | 5 +++++ tests/test_action.py | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/ereuse_devicehub/resources/action/schemas.py b/ereuse_devicehub/resources/action/schemas.py index 0c69aad9..3dc22160 100644 --- a/ereuse_devicehub/resources/action/schemas.py +++ b/ereuse_devicehub/resources/action/schemas.py @@ -426,6 +426,11 @@ class Ready(ActionWithMultipleDevices): class ActionStatus(ActionWithMultipleDevices): rol_user = NestedOn(s_user.User, dump_only=True, exclude=('token',)) + documents = NestedOn(s_document.TradeDocument, + many=True, + required=False, # todo test ensuring len(devices) >= 1 + only_query='id', + collection_class=OrderedSet) @post_load def put_rol_user(self, data: dict): diff --git a/tests/test_action.py b/tests/test_action.py index f82d0bac..d0decb93 100644 --- a/tests/test_action.py +++ b/tests/test_action.py @@ -470,6 +470,25 @@ def test_use_changing_owner(user: UserClient, user2: UserClient): assert device.owner.email == user2.email +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_recycling_container(user: UserClient): + lot, _ = user.post({'name': 'MyLotOut'}, res=Lot) + url = 'http://www.ereuse.org/', + request_post = { + 'filename': 'test.pdf', + 'hash': 'bbbbbbbb', + 'url': url, + 'weight': 150, + 'lot': lot['id'] + } + tradedocument, _ = user.post(res=TradeDocument, data=request_post) + action = {'type': models.Recycling.t, 'devices': [], 'documents': [tradedocument['id']]} + action, _ = user.post(action, res=models.Action) + trade = TradeDocument.query.one() + assert str(trade.actions[0].id) == action['id'] + + @pytest.mark.mvp def test_reuse(user: UserClient): snap, _ = user.post(file('basic.snapshot'), res=models.Snapshot) From 6599f4b66c524b4b2506f194a9186f4ddaf9a1d8 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 1 Oct 2021 10:46:08 +0200 Subject: [PATCH 12/17] fixing bug --- .../versions/a0978ac6cf4a_adding_state_actions.py | 2 +- ereuse_devicehub/resources/action/schemas.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ereuse_devicehub/migrations/versions/a0978ac6cf4a_adding_state_actions.py b/ereuse_devicehub/migrations/versions/a0978ac6cf4a_adding_state_actions.py index 02422946..963611ad 100644 --- a/ereuse_devicehub/migrations/versions/a0978ac6cf4a_adding_state_actions.py +++ b/ereuse_devicehub/migrations/versions/a0978ac6cf4a_adding_state_actions.py @@ -12,7 +12,7 @@ from sqlalchemy.dialects import postgresql # revision identifiers, used by Alembic. revision = 'a0978ac6cf4a' -down_revision = '7ecb8ff7abad' +down_revision = '3ac2bc1897ce' branch_labels = None depends_on = None diff --git a/ereuse_devicehub/resources/action/schemas.py b/ereuse_devicehub/resources/action/schemas.py index 3dc22160..c8e06b75 100644 --- a/ereuse_devicehub/resources/action/schemas.py +++ b/ereuse_devicehub/resources/action/schemas.py @@ -424,14 +424,24 @@ class Ready(ActionWithMultipleDevices): __doc__ = m.Ready.__doc__ -class ActionStatus(ActionWithMultipleDevices): +class ActionStatus(Action): rol_user = NestedOn(s_user.User, dump_only=True, exclude=('token',)) + devices = NestedOn(s_device.Device, + many=True, + required=False, # todo test ensuring len(devices) >= 1 + only_query='id', + collection_class=OrderedSet) documents = NestedOn(s_document.TradeDocument, many=True, required=False, # todo test ensuring len(devices) >= 1 only_query='id', collection_class=OrderedSet) + @pre_load + def put_devices(self, data: dict): + if not 'devices' in data.keys(): + data['devices'] = [] + @post_load def put_rol_user(self, data: dict): for dev in data['devices']: From e30c6c22503418521903671adb133e464f2fdc34 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 1 Oct 2021 11:26:56 +0200 Subject: [PATCH 13/17] fixing test --- tests/test_basic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_basic.py b/tests/test_basic.py index c3dc44d3..bdf48b8d 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -122,4 +122,4 @@ def test_api_docs(client: Client): 'scheme': 'basic', 'name': 'Authorization' } - assert len(docs['definitions']) == 127 + assert len(docs['definitions']) == 131 From 7a43eaa30fef40b2be7a01da5a7e630374ff395b Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Fri, 1 Oct 2021 11:42:31 +0200 Subject: [PATCH 14/17] fixing name reuse in test --- tests/test_action.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_action.py b/tests/test_action.py index d0decb93..2ecb6faf 100644 --- a/tests/test_action.py +++ b/tests/test_action.py @@ -492,7 +492,7 @@ def test_recycling_container(user: UserClient): @pytest.mark.mvp def test_reuse(user: UserClient): snap, _ = user.post(file('basic.snapshot'), res=models.Snapshot) - action = {'type': models.Reuse.t, 'devices': [snap['device']['id']]} + action = {'type': models.Use.t, 'devices': [snap['device']['id']]} action, _ = user.post(action, res=models.Action) device, _ = user.get(res=Device, item=snap['device']['devicehubID']) assert device['actions'][-1]['id'] == action['id'] From cae0ca8711e746e8aa71176c82e3abcb3a8b577d Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Wed, 6 Oct 2021 12:52:04 +0200 Subject: [PATCH 15/17] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b8dc6ce..332aa904 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ml). ## [1.0.10-beta] - [bugfix] #168 can to do a trade without devices. +- [addend] #167 new actions of status devices: use, recycling, refurbish and management. ## [1.0.9-beta] - [addend] #159 external document as proof of erase of disk From 2d37baca8d33f1cba77f2b0051ecd9782f358807 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Mon, 18 Oct 2021 11:20:05 +0200 Subject: [PATCH 16/17] fixing minor changes of review --- ereuse_devicehub/resources/action/models.py | 3 +- tests/test_action.py | 38 +++++++++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/ereuse_devicehub/resources/action/models.py b/ereuse_devicehub/resources/action/models.py index 853fd49b..66f69e9e 100644 --- a/ereuse_devicehub/resources/action/models.py +++ b/ereuse_devicehub/resources/action/models.py @@ -1351,7 +1351,6 @@ class DataWipe(JoinedTableMixin, ActionWithMultipleDevices): class ActionStatus(JoinedTableMixin, ActionWithMultipleTradeDocuments): -# class ActionStatus(JoinedTableMixin, ActionWithMultipleDevices): """This is a meta-action than mark the status of the devices""" rol_user_id = db.Column(UUID(as_uuid=True), @@ -1500,7 +1499,7 @@ class CancelReservation(Organize): class ActionStatusDocuments(JoinedTableMixin, ActionWithMultipleTradeDocuments): - """This is a meta-action than mark the status of the devices""" + """This is a meta-action that marks the state of the devices.""" rol_user_id = db.Column(UUID(as_uuid=True), db.ForeignKey(User.id), nullable=False, diff --git a/tests/test_action.py b/tests/test_action.py index f291d6e2..825bdb0a 100644 --- a/tests/test_action.py +++ b/tests/test_action.py @@ -473,6 +473,7 @@ def test_use_changing_owner(user: UserClient, user2: UserClient): @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_recycling_container(user: UserClient): + """Test of status action recycling for a container.""" lot, _ = user.post({'name': 'MyLotOut'}, res=Lot) url = 'http://www.ereuse.org/', request_post = { @@ -490,9 +491,42 @@ def test_recycling_container(user: UserClient): @pytest.mark.mvp -def test_reuse(user: UserClient): +@pytest.mark.parametrize('action_model', + (pytest.param(ams, id=ams.__class__.__name__) + for ams in [ + models.Recycling, + models.Use, + models.Refurbish, + models.Management + ])) +def test_status_without_lot(action_model: models.Action, user: UserClient): + """Test of status actions for devices without lot.""" snap, _ = user.post(file('basic.snapshot'), res=models.Snapshot) - action = {'type': models.Use.t, 'devices': [snap['device']['id']]} + action = {'type': action_model.t, 'devices': [snap['device']['id']]} + action, _ = user.post(action, res=models.Action) + device, _ = user.get(res=Device, item=snap['device']['devicehubID']) + assert device['actions'][-1]['id'] == action['id'] + + +@pytest.mark.mvp +@pytest.mark.parametrize('action_model', + (pytest.param(ams, id=ams.__class__.__name__) + for ams in [ + models.Recycling, + models.Use, + models.Refurbish, + models.Management + ])) +def test_status_in_temporary_lot(action_model: models.Action, user: UserClient): + """Test of status actions for devices in a temporary lot.""" + snap, _ = user.post(file('basic.snapshot'), res=models.Snapshot) + device_id = snap['device']['id'] + lot, _ = user.post({'name': 'MyLotOut'}, res=Lot) + lot, _ = user.post({}, + res=Lot, + item='{}/devices'.format(lot['id']), + query=[('id', device_id)]) + action = {'type': action_model.t, 'devices': [device_id]} action, _ = user.post(action, res=models.Action) device, _ = user.get(res=Device, item=snap['device']['devicehubID']) assert device['actions'][-1]['id'] == action['id'] From 715c87a04c60c0b4fb4e60d782aff1cee667d548 Mon Sep 17 00:00:00 2001 From: Cayo Puigdefabregas Date: Mon, 18 Oct 2021 11:22:31 +0200 Subject: [PATCH 17/17] CHANGELOG.md --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 332aa904..a7548f9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,6 @@ ml). ## testing [1.0.10-beta] -- [addend] #166 new action recycling and reuse ## [1.0.10-beta] - [bugfix] #168 can to do a trade without devices.