diff --git a/CHANGELOG.md b/CHANGELOG.md index b5709f91..3b8dc6ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ml). - [addend] #166 new action recycling and reuse ## [1.0.10-beta] +- [bugfix] #168 can to do a trade without devices. ## [1.0.9-beta] - [addend] #159 external document as proof of erase of disk diff --git a/ereuse_devicehub/resources/action/models.py b/ereuse_devicehub/resources/action/models.py index 64affa92..853fd49b 100644 --- a/ereuse_devicehub/resources/action/models.py +++ b/ereuse_devicehub/resources/action/models.py @@ -1706,7 +1706,10 @@ class MoveOnDocument(JoinedTableMixin, ActionWithMultipleTradeDocuments): ) container_from = db.relationship( 'TradeDocument', - primaryjoin='MoveOnDocument.container_from_id == TradeDocument.id', + backref=backref('containers_from', + lazy=True, + cascade=CASCADE_OWN), + primaryjoin='MoveOnDocument.container_from_id == TradeDocument.id' ) container_from_id.comment = """This is the trade document used as container in a incoming lot""" @@ -1717,6 +1720,9 @@ class MoveOnDocument(JoinedTableMixin, ActionWithMultipleTradeDocuments): ) container_to = db.relationship( 'TradeDocument', + backref=backref('containers_to', + lazy=True, + cascade=CASCADE_OWN), primaryjoin='MoveOnDocument.container_to_id == TradeDocument.id', ) container_to_id.comment = """This is the trade document used as container in a outgoing lot""" diff --git a/ereuse_devicehub/resources/action/schemas.py b/ereuse_devicehub/resources/action/schemas.py index c8e06b75..7c594375 100644 --- a/ereuse_devicehub/resources/action/schemas.py +++ b/ereuse_devicehub/resources/action/schemas.py @@ -751,6 +751,11 @@ class Trade(ActionWithMultipleDevices): required=True, only_query='id') + @pre_load + def adding_devices(self, data: dict): + if not 'devices' in data.keys(): + data['devices'] = [] + @validates_schema def validate_lot(self, data: dict): if not g.user.email in [data['user_from_email'], data['user_to_email']]: diff --git a/ereuse_devicehub/resources/device/models.py b/ereuse_devicehub/resources/device/models.py index 6fce9e89..76552321 100644 --- a/ereuse_devicehub/resources/device/models.py +++ b/ereuse_devicehub/resources/device/models.py @@ -382,13 +382,6 @@ class Device(Thing): if action.type == 'Revoke': return action.id - @property - def confirm_status(self): - """The actual state of confirmation of one Trade, or None if no Trade action - has ever been performed to this device.""" - # TODO @cayop we need implement this functionality - return None - @property def physical(self): """The actual physical state, None otherwise.""" diff --git a/ereuse_devicehub/resources/tradedocument/models.py b/ereuse_devicehub/resources/tradedocument/models.py index 36ae370a..177b2bd3 100644 --- a/ereuse_devicehub/resources/tradedocument/models.py +++ b/ereuse_devicehub/resources/tradedocument/models.py @@ -1,3 +1,5 @@ +import copy +from contextlib import suppress from citext import CIText from flask import g @@ -101,10 +103,10 @@ class TradeDocument(Thing): revoke = 'Revoke' revoke_pending = 'Revoke Pending' confirm_revoke = 'Document Revoked' - if not self.actions: + ac = self.last_action_trading() + if not ac: return - ac = self.actions[-1] if ac.type == 'ConfirmRevokeDocument': # can to do revoke_confirmed @@ -143,6 +145,18 @@ class TradeDocument(Thing): return weight + def last_action_trading(self): + """which is the last action trading""" + with suppress(StopIteration, ValueError): + actions = copy.copy(self.actions) + actions.sort(key=lambda x: x.created) + t_trades = ['Trade', + 'Confirm', + 'ConfirmRevokeDocument', + 'RevokeDocument', + 'ConfirmDocument'] + return next(e for e in reversed(actions) if e.t in t_trades) + def _warning_actions(self, actions): """Show warning actions""" return sorted(ev for ev in actions if ev.severity >= Severity.Warning) diff --git a/tests/test_action.py b/tests/test_action.py index 2ecb6faf..f291d6e2 100644 --- a/tests/test_action.py +++ b/tests/test_action.py @@ -1013,6 +1013,27 @@ def test_trade_endpoint(user: UserClient, user2: UserClient): device2, _ = user2.get(res=Device, item=device['id']) assert device2['id'] == device['id'] + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_trade_without_device(user: UserClient, user2: UserClient): + """Test one offer with automatic confirmation and without user to""" + lot, _ = user.post({'name': 'MyLot'}, res=Lot) + + request_post = { + 'type': 'Trade', + 'userFromEmail': user.email, + 'userToEmail': user2.email, + 'lot': lot['id'], + 'confirms': True, + } + + user.post(res=models.Action, data=request_post) + + trade = models.Trade.query.one() + assert str(trade.lot.id) == lot['id'] + + @pytest.mark.mvp @pytest.mark.usefixtures(conftest.app_context.__name__) def test_offer_without_to(user: UserClient): @@ -2742,3 +2763,61 @@ def test_moveOnDocument(user: UserClient, user2: UserClient): assert description == mvs.description tradedocument_to, _ = user.post(res=TradeDocument, data=request_post2) user.post(res=models.Action, data=request_moveOn, status=422) + + +@pytest.mark.mvp +@pytest.mark.usefixtures(conftest.app_context.__name__) +def test_moveOnDocument_bug168(user: UserClient, user2: UserClient): + """If you use one moveOnDocument in a trade Document. Next you can not drop this document.""" + lotIn, _ = user.post({'name': 'MyLotIn'}, res=Lot) + lotOut, _ = user.post({'name': 'MyLotOut'}, res=Lot) + url = 'http://www.ereuse.org/apapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaapaapaaaa', + request_post1 = { + 'filename': 'test.pdf', + 'hash': 'bbbbbbbb', + 'url': url, + 'weight': 150, + 'lot': lotIn['id'] + } + tradedocument_from, _ = user.post(res=TradeDocument, data=request_post1) + id_hash = 'aaaaaaaaa' + request_post2 = { + 'filename': 'test.pdf', + 'hash': id_hash, + 'url': url, + 'weight': 0, + 'lot': lotOut['id'] + } + tradedocument_to, _ = user.post(res=TradeDocument, data=request_post2) + + request_trade = { + 'type': 'Trade', + 'devices': [], + 'userFromEmail': user2.email, + 'userToEmail': user.email, + 'price': 10, + 'date': "2020-12-01T02:00:00+00:00", + 'lot': lotIn['id'], + 'confirms': True, + } + + user.post(res=models.Action, data=request_trade) + + description = 'This is a good description' + request_moveOn = { + 'type': 'MoveOnDocument', + 'weight': 4, + 'container_from': tradedocument_from['id'], + 'container_to': id_hash, + 'description': description + } + doc, _ = user.post(res=models.Action, data=request_moveOn) + trade = models.Trade.query.one() + trade_document1 = TradeDocument.query.filter_by(id=tradedocument_from['id']).one() + trade_document2 = TradeDocument.query.filter_by(id=tradedocument_to['id']).one() + assert trade_document1.total_weight == 150.0 + assert trade_document2.total_weight == 4.0 + assert trade_document1.trading == 'Confirm' + assert trade_document2.trading == None + + tradedocument, _ = user.delete(res=TradeDocument, item=tradedocument_to['id'])