fixing revokeView and reset_owner

This commit is contained in:
Cayo Puigdefabregas 2021-11-10 18:59:44 +01:00
parent f06007e199
commit 9dc79a59dc
11 changed files with 188 additions and 111 deletions

View File

@ -0,0 +1,43 @@
"""adding author action_device
Revision ID: d22d230d2850
Revises: 1bb2b5e0fae7
Create Date: 2021-11-10 17:37:12.304853
"""
import sqlalchemy as sa
from alembic import context
from alembic import op
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = 'd22d230d2850'
down_revision = '1bb2b5e0fae7'
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.add_column('action_device',
sa.Column('author_id',
postgresql.UUID(),
nullable=True),
schema=f'{get_inv()}')
op.create_foreign_key("fk_action_device_author",
"action_device", "user",
["author_id"], ["id"],
ondelete="SET NULL",
source_schema=f'{get_inv()}',
referent_schema='common')
def downgrade():
op.drop_constraint("fk_action_device_author", "device", type_="foreignkey", schema=f'{get_inv()}')
op.drop_column('action_device', 'author_id', schema=f'{get_inv()}')

View File

@ -285,6 +285,11 @@ class RevokeDef(ActionDef):
SCHEMA = schemas.Revoke
class ConfirmRevokeDef(ActionDef):
VIEW = None
SCHEMA = schemas.ConfirmRevoke
class TradeDef(ActionDef):
VIEW = None
SCHEMA = schemas.Trade

View File

@ -317,6 +317,14 @@ class ActionDevice(db.Model):
index=True,
server_default=db.text('CURRENT_TIMESTAMP'))
created.comment = """When Devicehub created this."""
author_id = Column(UUID(as_uuid=True),
ForeignKey(User.id),
nullable=False,
default=lambda: g.user.id)
# todo compute the org
author = relationship(User,
backref=backref('authored_actions_device', lazy=True, collection_class=set),
primaryjoin=author_id == User.id)
def __init__(self, **kwargs) -> None:
self.created = kwargs.get('created', datetime.now(timezone.utc))

View File

@ -445,16 +445,13 @@ class ActionStatus(Action):
@post_load
def put_rol_user(self, data: dict):
for dev in data['devices']:
if dev.trading in [None, 'Revoke']:
return data
trades = [ac for ac in dev.actions if ac.t == 'Trade']
if not trades:
return data
trade = trades[-1]
if trade.user_to != g.user:
if trade.user_from == g.user:
data['rol_user'] = trade.user_to
data['trade'] = trade
@ -588,6 +585,10 @@ class Revoke(ActionWithMultipleDevices):
raise ValidationError(txt)
class ConfirmRevoke(Revoke):
pass
class ConfirmDocument(ActionWithMultipleDocuments):
__doc__ = m.Confirm.__doc__
action = NestedOn('Action', only_query='id')

View File

@ -220,15 +220,29 @@ class RevokeView(ConfirmMixin):
raise ValidationError('Devices not exist.')
lot = data['action'].lot
revokeConfirmed = []
for dev in data['devices']:
if not dev.trading(lot) == 'TradeConfirmed':
txt = 'Some of devices do not have enough to confirm for to do a revoke'
ValidationError(txt)
if dev.trading(lot) == 'RevokeConfirmed':
# this device is revoked before
revokeConfirmed.append(dev)
### End check ###
ids = {d.id for d in data['devices']}
lot = data['action'].lot
self.model = delete_from_trade(lot, ids)
devices = {d for d in data['devices'] if d not in revokeConfirmed}
# self.model = delete_from_trade(lot, devices)
# TODO @cayop we dont need delete_from_trade
drop_of_lot = []
# import pdb; pdb.set_trace()
for dev in devices:
# import pdb; pdb.set_trace()
if dev.trading_for_web(lot) in ['NeedConfirmation', 'Confirm', 'NeedConfirmRevoke']:
drop_of_lot.append(dev)
dev.reset_owner()
self.model = Revoke(action=lot.trade, user=g.user, devices=devices)
db.session.add(self.model)
lot.devices.difference_update(OrderedSet(drop_of_lot))
# class ConfirmRevokeView(ConfirmMixin):

View File

@ -235,6 +235,10 @@ class ActionView(View):
revoke = trade_view.RevokeView(json, resource_def, self.schema)
return revoke.post()
if json['type'] == 'ConfirmRevoke':
revoke = trade_view.RevokeView(json, resource_def, self.schema)
return revoke.post()
if json['type'] == 'RevokeDocument':
revoke = trade_view.RevokeDocumentView(json, resource_def, self.schema)
return revoke.post()

View File

@ -89,7 +89,6 @@ class Metrics(MetricsMix):
trade['status_receiver_created'] = self.act.created
return
# import pdb; pdb.set_trace()
# necesitamos poder poner un cambio de estado de un trade mas antiguo que last_trade
# lo mismo con confirm
@ -148,7 +147,8 @@ class Metrics(MetricsMix):
if the action is one trade action, is possible than have a list of confirmations.
Get the doble confirm for to know if this trade is confirmed or not.
"""
if self.device.trading == 'TradeConfirmed':
# import pdb; pdb.set_trace()
if self.device.trading(self.act.lot) == 'TradeConfirmed':
return True
return False

View File

@ -310,6 +310,80 @@ class Device(Thing):
return history
@property
def tradings(self):
return {str(x.id): self.trading_for_web(x.lot) for x in self.actions if x.t == 'Trade'}
def trading_for_web(self, lot):
"""The trading state, or None if no Trade action has
ever been performed to this device. This extract the posibilities for to do.
This method is performed for show in the web."""
if not hasattr(lot, 'trade'):
return
Status = {0: 'Trade',
1: 'Confirm',
2: 'NeedConfirmation',
3: 'TradeConfirmed',
4: 'Revoke',
5: 'NeedConfirmRevoke',
6: 'RevokeConfirmed'}
trade = lot.trade
user_from = trade.user_from
user_to = trade.user_to
user_from_confirm = False
user_to_confirm = False
user_from_revoke = False
user_to_revoke = False
status = 0
if not hasattr(trade, 'acceptances'):
return Status[status]
for ac in self.actions:
if ac.t not in ['Confirm', 'Revoke']:
continue
if ac.user not in [user_from, user_to]:
continue
if ac.t == 'Confirm' and ac.action == trade:
if ac.user == user_from:
user_from_confirm = True
elif ac.user == user_to:
user_to_confirm = True
if ac.t == 'Revoke' and ac.action == trade:
if ac.user == user_from:
user_from_revoke = True
elif ac.user == user_to:
user_to_revoke = True
confirms = [user_from_confirm, user_to_confirm]
revokes = [user_from_revoke, user_to_revoke]
if any(confirms):
status = 1
if user_to_confirm and user_from == g.user:
status = 2
if user_from_confirm and user_to == g.user:
status = 2
if all(confirms):
status = 3
if any(revokes):
status = 4
if user_to_revoke and user_from == g.user:
status = 5
if user_from_revoke and user_to == g.user:
status = 5
if all(revokes):
status = 6
return Status[status]
def trading(self, lot):
"""The trading state, or None if no Trade action has
ever been performed to this device. This extract the posibilities for to do"""
@ -330,30 +404,28 @@ class Device(Thing):
user_from_revoke = False
user_to_revoke = False
status = 0
confirms = {}
revokes = {}
if not hasattr(trade, 'acceptances'):
return Status[status]
acceptances = copy.copy(trade.acceptances)
acceptances = sorted(acceptances, key=lambda x: x.created)
for ac in self.actions:
if ac.t not in ['Confirm', 'Revoke']:
continue
for ac in acceptances:
if ac.user not in [user_from, user_to]:
continue
if ac.t == 'Confirm':
if ac.t == 'Confirm' and ac.action == trade:
if ac.user == user_from:
user_from_confirm = True
elif ac.user == user_to:
user_to_confirm = True
if ac.t == 'Revoke':
if ac.t == 'Revoke' and ac.action == trade:
if ac.user == user_from:
user_from_revoke = True
elif ac.user == user_to:
user_to_revoke= True
user_to_revoke = True
confirms = [user_from_confirm, user_to_confirm]
revokes = [user_from_revoke, user_to_revoke]
@ -370,75 +442,6 @@ class Device(Thing):
return Status[status]
def trading2(self):
"""The trading state, or None if no Trade action has
ever been performed to this device. This extract the posibilities for to do"""
# trade = 'Trade'
confirm = 'Confirm'
need_confirm = 'NeedConfirmation'
double_confirm = 'TradeConfirmed'
revoke = 'Revoke'
revoke_pending = 'RevokePending'
confirm_revoke = 'ConfirmRevoke'
# revoke_confirmed = 'RevokeConfirmed'
# return the correct status of trade depending of the user
# #### CASES #####
# User1 == owner of trade (This user have automatic Confirmation)
# =======================
# if the last action is => only allow to do
# ==========================================
# Confirmation not User1 => Revoke
# Confirmation User1 => Revoke
# Revoke not User1 => ConfirmRevoke
# Revoke User1 => RevokePending
# RevokeConfirmation => RevokeConfirmed
#
#
# User2 == Not owner of trade
# =======================
# if the last action is => only allow to do
# ==========================================
# Confirmation not User2 => Confirm
# Confirmation User2 => Revoke
# Revoke not User2 => ConfirmRevoke
# Revoke User2 => RevokePending
# RevokeConfirmation => RevokeConfirmed
ac = self.last_action_trading
if not ac:
return
first_owner = self.which_user_put_this_device_in_trace()
if ac.type == confirm_revoke:
# can to do revoke_confirmed
return confirm_revoke
if ac.type == revoke:
if ac.user == g.user:
# can todo revoke_pending
return revoke_pending
else:
# can to do confirm_revoke
return revoke
if ac.type == confirm:
if not first_owner:
return
if ac.user == first_owner:
if first_owner == g.user:
# can to do revoke
return confirm
else:
# can to do confirm
return need_confirm
else:
# can to do revoke
return double_confirm
@property
def revoke(self):
"""If the actual trading state is an revoke action, this property show
@ -543,15 +546,16 @@ class Device(Thing):
def which_user_put_this_device_in_trace(self):
"""which is the user than put this device in this trade"""
actions = copy.copy(self.actions)
actions.sort(key=lambda x: x.created)
actions.reverse()
last_ac = None
# search the automatic Confirm
for ac in actions:
if ac.type == 'Trade':
return last_ac.user
if ac.type == 'Confirm':
last_ac = ac
# import pdb; pdb.set_trace()
action_device = [x.device for x in ac.actions_device if x.device == self][0]
if action_device.author:
return action_device.author
return ac.author
def change_owner(self, new_user):
"""util for change the owner one device"""

View File

@ -1,7 +1,7 @@
import datetime
from marshmallow import post_load, pre_load, fields as f
from marshmallow.fields import Boolean, Date, DateTime, Float, Integer, List, Str, String, UUID
from marshmallow.fields import Boolean, Date, DateTime, Float, Integer, List, Str, String, UUID, Dict
from marshmallow.validate import Length, OneOf, Range
from sqlalchemy.util import OrderedSet
from stdnum import imei, meid
@ -50,12 +50,11 @@ class Device(Thing):
description='The lots where this device is directly under.')
rate = NestedOn('Rate', dump_only=True, description=m.Device.rate.__doc__)
price = NestedOn('Price', dump_only=True, description=m.Device.price.__doc__)
# trading = EnumField(states.Trading, dump_only=True, description=m.Device.trading.__doc__)
trading = SanitizedStr(dump_only=True, description='')
tradings = Dict(dump_only=True, description='')
physical = EnumField(states.Physical, dump_only=True, description=m.Device.physical.__doc__)
traking= EnumField(states.Traking, dump_only=True, description=m.Device.physical.__doc__)
traking = EnumField(states.Traking, dump_only=True, description=m.Device.physical.__doc__)
usage = EnumField(states.Usage, dump_only=True, description=m.Device.physical.__doc__)
revoke = UUID(dump_only=True)
revoke = UUID(dump_only=True)
physical_possessor = NestedOn('Agent', dump_only=True, data_key='physicalPossessor')
production_date = DateTime('iso',
description=m.Device.updated.comment,

View File

@ -305,6 +305,7 @@ def delete_from_trade(lot: Lot, ids: Set[int]):
db.session.add(phantom_revoke)
lot.devices.difference_update(without_confirms)
# TODO @cayop ?? we dont need this line
lot.trade.devices = lot.devices
return revoke

View File

@ -1516,8 +1516,8 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient):
'type': 'Confirm',
'action': trade.id,
'devices': [
snap1['device']['id'],
snap2['device']['id'],
snap1['device']['id'],
snap2['device']['id'],
snap3['device']['id'],
snap4['device']['id'],
snap5['device']['id'],
@ -1535,7 +1535,7 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient):
assert trade.devices[-1].actions[-1].user == trade.user_from
assert len(trade.devices[0].actions) == n_actions
# The manager remove one device of the lot and automaticaly
# The manager remove one device of the lot and automaticaly
# is create one revoke action
device_10 = trade.devices[-1]
lot, _ = user.delete({},
@ -1554,31 +1554,28 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient):
# the SCRAP confirms the revoke action
request_confirm_revoke = {
'type': 'ConfirmRevoke',
'action': device_10.actions[-1].id,
'type': 'Revoke',
'action': trade.id,
'devices': [
snap10['device']['id']
]
}
user2.post(res=models.Action, data=request_confirm_revoke)
assert device_10.actions[-1].t == 'ConfirmRevoke'
assert device_10.actions[-1].t == 'Revoke'
assert device_10.actions[-2].t == 'Revoke'
# assert len(trade.lot.devices) == len(trade.devices) == 9
# assert not device_10 in trade.devices
# check validation error
request_confirm_revoke = {
'type': 'ConfirmRevoke',
'action': device_10.actions[-1].id,
'type': 'Revoke',
'action': trade.id,
'devices': [
snap9['device']['id']
]
}
user2.post(res=models.Action, data=request_confirm_revoke, status=422)
# The manager add again device_10
# assert len(trade.devices) == 9
lot, _ = user.post({},
@ -1604,7 +1601,7 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient):
assert device_10.actions[-1].user == trade.user_from
assert device_10.actions[-2].t == 'Confirm'
assert device_10.actions[-2].user == trade.user_to
assert device_10.actions[-3].t == 'ConfirmRevoke'
assert device_10.actions[-3].t == 'Revoke'
# assert len(device_10.actions) == 13
@ -1712,6 +1709,7 @@ def test_confirmRevoke(user: UserClient, user2: UserClient):
assert len(trade.devices) == 10
# the SCRAP confirms the revoke action
import pdb; pdb.set_trace()
request_confirm_revoke = {
'type': 'ConfirmRevoke',
'action': device_10.actions[-2].id,