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 SCHEMA = schemas.Revoke
class ConfirmRevokeDef(ActionDef):
VIEW = None
SCHEMA = schemas.ConfirmRevoke
class TradeDef(ActionDef): class TradeDef(ActionDef):
VIEW = None VIEW = None
SCHEMA = schemas.Trade SCHEMA = schemas.Trade

View File

@ -317,6 +317,14 @@ class ActionDevice(db.Model):
index=True, index=True,
server_default=db.text('CURRENT_TIMESTAMP')) server_default=db.text('CURRENT_TIMESTAMP'))
created.comment = """When Devicehub created this.""" 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: def __init__(self, **kwargs) -> None:
self.created = kwargs.get('created', datetime.now(timezone.utc)) self.created = kwargs.get('created', datetime.now(timezone.utc))

View File

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

View File

@ -220,15 +220,29 @@ class RevokeView(ConfirmMixin):
raise ValidationError('Devices not exist.') raise ValidationError('Devices not exist.')
lot = data['action'].lot lot = data['action'].lot
revokeConfirmed = []
for dev in data['devices']: for dev in data['devices']:
if not dev.trading(lot) == 'TradeConfirmed': if dev.trading(lot) == 'RevokeConfirmed':
txt = 'Some of devices do not have enough to confirm for to do a revoke' # this device is revoked before
ValidationError(txt) revokeConfirmed.append(dev)
### End check ### ### End check ###
ids = {d.id for d in data['devices']} devices = {d for d in data['devices'] if d not in revokeConfirmed}
lot = data['action'].lot # self.model = delete_from_trade(lot, devices)
self.model = delete_from_trade(lot, ids) # 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): # class ConfirmRevokeView(ConfirmMixin):

View File

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

View File

@ -89,7 +89,6 @@ class Metrics(MetricsMix):
trade['status_receiver_created'] = self.act.created trade['status_receiver_created'] = self.act.created
return return
# import pdb; pdb.set_trace()
# necesitamos poder poner un cambio de estado de un trade mas antiguo que last_trade # necesitamos poder poner un cambio de estado de un trade mas antiguo que last_trade
# lo mismo con confirm # 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. 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. 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 True
return False return False

View File

@ -310,6 +310,80 @@ class Device(Thing):
return history 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): def trading(self, lot):
"""The trading state, or None if no Trade action has """The trading state, or None if no Trade action has
ever been performed to this device. This extract the posibilities for to do""" 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_from_revoke = False
user_to_revoke = False user_to_revoke = False
status = 0 status = 0
confirms = {}
revokes = {}
if not hasattr(trade, 'acceptances'): if not hasattr(trade, 'acceptances'):
return Status[status] return Status[status]
acceptances = copy.copy(trade.acceptances) for ac in self.actions:
acceptances = sorted(acceptances, key=lambda x: x.created) if ac.t not in ['Confirm', 'Revoke']:
continue
for ac in acceptances:
if ac.user not in [user_from, user_to]: if ac.user not in [user_from, user_to]:
continue continue
if ac.t == 'Confirm': if ac.t == 'Confirm' and ac.action == trade:
if ac.user == user_from: if ac.user == user_from:
user_from_confirm = True user_from_confirm = True
elif ac.user == user_to: elif ac.user == user_to:
user_to_confirm = True user_to_confirm = True
if ac.t == 'Revoke': if ac.t == 'Revoke' and ac.action == trade:
if ac.user == user_from: if ac.user == user_from:
user_from_revoke = True user_from_revoke = True
elif ac.user == user_to: elif ac.user == user_to:
user_to_revoke= True user_to_revoke = True
confirms = [user_from_confirm, user_to_confirm] confirms = [user_from_confirm, user_to_confirm]
revokes = [user_from_revoke, user_to_revoke] revokes = [user_from_revoke, user_to_revoke]
@ -370,75 +442,6 @@ class Device(Thing):
return Status[status] 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 @property
def revoke(self): def revoke(self):
"""If the actual trading state is an revoke action, this property show """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): def which_user_put_this_device_in_trace(self):
"""which is the user than put this device in this trade""" """which is the user than put this device in this trade"""
actions = copy.copy(self.actions) actions = copy.copy(self.actions)
actions.sort(key=lambda x: x.created)
actions.reverse() actions.reverse()
last_ac = None
# search the automatic Confirm # search the automatic Confirm
for ac in actions: for ac in actions:
if ac.type == 'Trade': if ac.type == 'Trade':
return last_ac.user # import pdb; pdb.set_trace()
if ac.type == 'Confirm': action_device = [x.device for x in ac.actions_device if x.device == self][0]
last_ac = ac if action_device.author:
return action_device.author
return ac.author
def change_owner(self, new_user): def change_owner(self, new_user):
"""util for change the owner one device""" """util for change the owner one device"""

View File

@ -1,7 +1,7 @@
import datetime import datetime
from marshmallow import post_load, pre_load, fields as f 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 marshmallow.validate import Length, OneOf, Range
from sqlalchemy.util import OrderedSet from sqlalchemy.util import OrderedSet
from stdnum import imei, meid from stdnum import imei, meid
@ -50,12 +50,11 @@ class Device(Thing):
description='The lots where this device is directly under.') description='The lots where this device is directly under.')
rate = NestedOn('Rate', dump_only=True, description=m.Device.rate.__doc__) rate = NestedOn('Rate', dump_only=True, description=m.Device.rate.__doc__)
price = NestedOn('Price', dump_only=True, description=m.Device.price.__doc__) price = NestedOn('Price', dump_only=True, description=m.Device.price.__doc__)
# trading = EnumField(states.Trading, dump_only=True, description=m.Device.trading.__doc__) tradings = Dict(dump_only=True, description='')
trading = SanitizedStr(dump_only=True, description='')
physical = EnumField(states.Physical, dump_only=True, description=m.Device.physical.__doc__) 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__) 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') physical_possessor = NestedOn('Agent', dump_only=True, data_key='physicalPossessor')
production_date = DateTime('iso', production_date = DateTime('iso',
description=m.Device.updated.comment, 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) db.session.add(phantom_revoke)
lot.devices.difference_update(without_confirms) lot.devices.difference_update(without_confirms)
# TODO @cayop ?? we dont need this line
lot.trade.devices = lot.devices lot.trade.devices = lot.devices
return revoke return revoke

View File

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