Merge pull request #145 from eReuse/feature/trade-documents
Feature/trade documents
This commit is contained in:
commit
bc0e94d973
|
@ -12,6 +12,7 @@ from ereuse_devicehub.resources import action, agent, deliverynote, inventory, \
|
|||
lot, tag, user
|
||||
from ereuse_devicehub.resources.device import definitions
|
||||
from ereuse_devicehub.resources.documents import documents
|
||||
from ereuse_devicehub.resources.tradedocument import definitions as tradedocument
|
||||
from ereuse_devicehub.resources.enums import PriceSoftware
|
||||
from ereuse_devicehub.resources.versions import versions
|
||||
from ereuse_devicehub.resources.licences import licences
|
||||
|
@ -27,6 +28,7 @@ class DevicehubConfig(Config):
|
|||
import_resource(lot),
|
||||
import_resource(deliverynote),
|
||||
import_resource(documents),
|
||||
import_resource(tradedocument),
|
||||
import_resource(inventory),
|
||||
import_resource(versions),
|
||||
import_resource(licences),
|
||||
|
@ -71,3 +73,6 @@ class DevicehubConfig(Config):
|
|||
|
||||
"""Admin email"""
|
||||
EMAIL_ADMIN = config('EMAIL_ADMIN', '')
|
||||
|
||||
"""Definition of path where save the documents of customers"""
|
||||
PATH_DOCUMENTS_STORAGE = config('PATH_DOCUMENTS_STORAGE', '/tmp/')
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
"""tradeDocuments
|
||||
|
||||
Revision ID: 3a3601ac8224
|
||||
Revises: 51439cf24be8
|
||||
Create Date: 2021-06-15 14:38:59.931818
|
||||
|
||||
"""
|
||||
import teal
|
||||
import citext
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from alembic import context
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '3a3601ac8224'
|
||||
down_revision = '51439cf24be8'
|
||||
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('trade_document',
|
||||
sa.Column(
|
||||
'updated',
|
||||
sa.TIMESTAMP(timezone=True),
|
||||
server_default=sa.text('CURRENT_TIMESTAMP'),
|
||||
nullable=False,
|
||||
comment='The last time Devicehub recorded a change for \n this thing.\n '
|
||||
),
|
||||
sa.Column(
|
||||
'created',
|
||||
sa.TIMESTAMP(timezone=True),
|
||||
server_default=sa.text('CURRENT_TIMESTAMP'),
|
||||
nullable=False,
|
||||
comment='When Devicehub created this.'
|
||||
),
|
||||
sa.Column(
|
||||
'id',
|
||||
sa.BigInteger(),
|
||||
nullable=False,
|
||||
comment='The identifier of the device for this database. Used only\n internally for software; users should not use this.\n '
|
||||
),
|
||||
sa.Column(
|
||||
'date',
|
||||
sa.DateTime(),
|
||||
nullable=True,
|
||||
comment='The date of document, some documents need to have one date\n '
|
||||
),
|
||||
sa.Column(
|
||||
'id_document',
|
||||
citext.CIText(),
|
||||
nullable=True,
|
||||
comment='The id of one document like invoice so they can be linked.'
|
||||
),
|
||||
sa.Column(
|
||||
'description',
|
||||
citext.CIText(),
|
||||
nullable=True,
|
||||
comment='A description of document.'
|
||||
),
|
||||
sa.Column(
|
||||
'owner_id',
|
||||
postgresql.UUID(as_uuid=True),
|
||||
nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
'lot_id',
|
||||
postgresql.UUID(as_uuid=True),
|
||||
nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
'file_name',
|
||||
citext.CIText(),
|
||||
nullable=True,
|
||||
comment='This is the name of the file when user up the document.'
|
||||
),
|
||||
sa.Column(
|
||||
'file_hash',
|
||||
citext.CIText(),
|
||||
nullable=True,
|
||||
comment='This is the hash of the file produced from frontend.'
|
||||
),
|
||||
sa.Column(
|
||||
'url',
|
||||
citext.CIText(),
|
||||
teal.db.URL(),
|
||||
nullable=True,
|
||||
comment='This is the url where resides the document.'
|
||||
),
|
||||
sa.ForeignKeyConstraint(['lot_id'], [f'{get_inv()}.lot.id'],),
|
||||
sa.ForeignKeyConstraint(['owner_id'], ['common.user.id'],),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
schema=f'{get_inv()}'
|
||||
)
|
||||
# Action document table
|
||||
op.create_table('action_trade_document',
|
||||
sa.Column('document_id', sa.BigInteger(), nullable=False),
|
||||
sa.Column('action_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.ForeignKeyConstraint(['action_id'], [f'{get_inv()}.action.id'], ),
|
||||
sa.ForeignKeyConstraint(['document_id'], [f'{get_inv()}.trade_document.id'], ),
|
||||
sa.PrimaryKeyConstraint('document_id', 'action_id'),
|
||||
schema=f'{get_inv()}'
|
||||
)
|
||||
|
||||
op.create_index('document_id', 'trade_document', ['id'], unique=False, postgresql_using='hash', schema=f'{get_inv()}')
|
||||
op.create_index(op.f('ix_trade_document_created'), 'trade_document', ['created'], unique=False, schema=f'{get_inv()}')
|
||||
op.create_index(op.f('ix_trade_document_updated'), 'trade_document', ['updated'], unique=False, schema=f'{get_inv()}')
|
||||
|
||||
op.create_table('confirm_document',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('action_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
|
||||
sa.ForeignKeyConstraint(['id'], [f'{get_inv()}.action.id'], ),
|
||||
sa.ForeignKeyConstraint(['action_id'], [f'{get_inv()}.action.id'], ),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['common.user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
schema=f'{get_inv()}'
|
||||
)
|
||||
|
||||
def downgrade():
|
||||
op.drop_table('action_trade_document', schema=f'{get_inv()}')
|
||||
op.drop_table('confirm_document', schema=f'{get_inv()}')
|
||||
op.drop_table('trade_document', schema=f'{get_inv()}')
|
||||
|
|
@ -5,11 +5,12 @@ Revises: eca457d8b2a4
|
|||
Create Date: 2021-03-15 17:40:34.410408
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
import citext
|
||||
import teal
|
||||
from alembic import op
|
||||
from alembic import context
|
||||
from sqlalchemy.dialects import postgresql
|
||||
import sqlalchemy as sa
|
||||
import citext
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
|
@ -83,7 +84,7 @@ def upgrade():
|
|||
schema=f'{get_inv()}'
|
||||
)
|
||||
|
||||
# ## User
|
||||
## User
|
||||
op.add_column('user', sa.Column('active', sa.Boolean(), default=True, nullable=True),
|
||||
schema='common')
|
||||
op.add_column('user', sa.Column('phantom', sa.Boolean(), default=False, nullable=True),
|
||||
|
|
|
@ -270,6 +270,21 @@ class TradeDef(ActionDef):
|
|||
SCHEMA = schemas.Trade
|
||||
|
||||
|
||||
class ConfirmDocumentDef(ActionDef):
|
||||
VIEW = None
|
||||
SCHEMA = schemas.ConfirmDocument
|
||||
|
||||
|
||||
class RevokeDocumentDef(ActionDef):
|
||||
VIEW = None
|
||||
SCHEMA = schemas.RevokeDocument
|
||||
|
||||
|
||||
class ConfirmRevokeDocumentDef(ActionDef):
|
||||
VIEW = None
|
||||
SCHEMA = schemas.ConfirmRevokeDocument
|
||||
|
||||
|
||||
class CancelTradeDef(ActionDef):
|
||||
VIEW = None
|
||||
SCHEMA = schemas.CancelTrade
|
||||
|
|
|
@ -48,6 +48,7 @@ from ereuse_devicehub.resources.enums import AppearanceRange, BatteryHealth, Bio
|
|||
TestDataStorageLength
|
||||
from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing
|
||||
from ereuse_devicehub.resources.user.models import User
|
||||
from ereuse_devicehub.resources.tradedocument.models import TradeDocument
|
||||
|
||||
|
||||
class JoinedTableMixin:
|
||||
|
@ -295,6 +296,20 @@ class ActionDevice(db.Model):
|
|||
primary_key=True)
|
||||
|
||||
|
||||
class ActionWithMultipleTradeDocuments(ActionWithMultipleDevices):
|
||||
documents = relationship(TradeDocument,
|
||||
backref=backref('actions_docs', lazy=True, **_sorted_actions),
|
||||
secondary=lambda: ActionTradeDocument.__table__,
|
||||
order_by=lambda: TradeDocument.id,
|
||||
collection_class=OrderedSet)
|
||||
|
||||
|
||||
class ActionTradeDocument(db.Model):
|
||||
document_id = Column(BigInteger, ForeignKey(TradeDocument.id), primary_key=True)
|
||||
action_id = Column(UUID(as_uuid=True), ForeignKey(ActionWithMultipleTradeDocuments.id),
|
||||
primary_key=True)
|
||||
|
||||
|
||||
class Add(ActionWithOneDevice):
|
||||
"""The act of adding components to a device.
|
||||
|
||||
|
@ -1433,6 +1448,43 @@ class CancelReservation(Organize):
|
|||
"""The act of cancelling a reservation."""
|
||||
|
||||
|
||||
class ConfirmDocument(JoinedTableMixin, ActionWithMultipleTradeDocuments):
|
||||
"""Users confirm the one action trade this confirmation it's link to trade
|
||||
and the document that confirm
|
||||
"""
|
||||
user_id = db.Column(UUID(as_uuid=True),
|
||||
db.ForeignKey(User.id),
|
||||
nullable=False,
|
||||
default=lambda: g.user.id)
|
||||
user = db.relationship(User, primaryjoin=user_id == User.id)
|
||||
user_comment = """The user that accept the offer."""
|
||||
action_id = db.Column(UUID(as_uuid=True),
|
||||
db.ForeignKey('action.id'),
|
||||
nullable=False)
|
||||
action = db.relationship('Action',
|
||||
backref=backref('acceptances_document',
|
||||
uselist=True,
|
||||
lazy=True,
|
||||
order_by=lambda: Action.end_time,
|
||||
collection_class=list),
|
||||
primaryjoin='ConfirmDocument.action_id == Action.id')
|
||||
|
||||
def __repr__(self) -> str:
|
||||
if self.action.t in ['Trade']:
|
||||
origin = 'To'
|
||||
if self.user == self.action.user_from:
|
||||
origin = 'From'
|
||||
return '<{0.t}app/views/inventory/ {0.id} accepted by {1}>'.format(self, origin)
|
||||
|
||||
|
||||
class RevokeDocument(ConfirmDocument):
|
||||
pass
|
||||
|
||||
|
||||
class ConfirmRevokeDocument(ConfirmDocument):
|
||||
pass
|
||||
|
||||
|
||||
class Confirm(JoinedTableMixin, ActionWithMultipleDevices):
|
||||
"""Users confirm the one action trade this confirmation it's link to trade
|
||||
and the devices that confirm
|
||||
|
@ -1473,7 +1525,7 @@ class ConfirmRevoke(Confirm):
|
|||
return '<{0.t} {0.id} accepted by {0.user}>'.format(self)
|
||||
|
||||
|
||||
class Trade(JoinedTableMixin, ActionWithMultipleDevices):
|
||||
class Trade(JoinedTableMixin, ActionWithMultipleTradeDocuments):
|
||||
"""Trade actions log the political exchange of devices between users.
|
||||
Every time a trade action is performed, the old user looses its
|
||||
political possession, for example ownership, in favor of another
|
||||
|
@ -1500,8 +1552,6 @@ class Trade(JoinedTableMixin, ActionWithMultipleDevices):
|
|||
currency = Column(DBEnum(Currency), nullable=False, default=Currency.EUR.name)
|
||||
currency.comment = """The currency of this price as for ISO 4217."""
|
||||
date = Column(db.TIMESTAMP(timezone=True))
|
||||
document_id = Column(CIText())
|
||||
document_id.comment = """The id of one document like invoice so they can be linked."""
|
||||
confirm = Column(Boolean, default=False, nullable=False)
|
||||
confirm.comment = """If you need confirmation of the user, you need actevate this field"""
|
||||
code = Column(CIText(), nullable=True)
|
||||
|
|
|
@ -16,6 +16,7 @@ from ereuse_devicehub.resources import enums
|
|||
from ereuse_devicehub.resources.action import models as m
|
||||
from ereuse_devicehub.resources.agent import schemas as s_agent
|
||||
from ereuse_devicehub.resources.device import schemas as s_device
|
||||
from ereuse_devicehub.resources.tradedocument import schemas as s_document
|
||||
from ereuse_devicehub.resources.enums import AppearanceRange, BiosAccessRange, FunctionalityRange, \
|
||||
PhysicalErasureMethod, R_POSITIVE, RatingRange, \
|
||||
Severity, SnapshotSoftware, TestDataStorageLength
|
||||
|
@ -58,6 +59,15 @@ class ActionWithOneDevice(Action):
|
|||
device = NestedOn(s_device.Device, only_query='id')
|
||||
|
||||
|
||||
class ActionWithMultipleDocuments(Action):
|
||||
__doc__ = m.ActionWithMultipleTradeDocuments.__doc__
|
||||
documents = NestedOn(s_document.TradeDocument,
|
||||
many=True,
|
||||
required=True, # todo test ensuring len(devices) >= 1
|
||||
only_query='id',
|
||||
collection_class=OrderedSet)
|
||||
|
||||
|
||||
class ActionWithMultipleDevices(Action):
|
||||
__doc__ = m.ActionWithMultipleDevices.__doc__
|
||||
devices = NestedOn(s_device.Device,
|
||||
|
@ -482,6 +492,126 @@ class Revoke(ActionWithMultipleDevices):
|
|||
txt = "Device {} not exist in the trade".format(dev.devicehub_id)
|
||||
raise ValidationError(txt)
|
||||
|
||||
for doc in data.get('documents', []):
|
||||
# if document not exist in the Trade, then this query is wrong
|
||||
if not doc in data['action'].documents:
|
||||
txt = "Document {} not exist in the trade".format(doc.file_name)
|
||||
raise ValidationError(txt)
|
||||
|
||||
@validates_schema
|
||||
def validate_documents(self, data):
|
||||
"""Check if there are or no one before confirmation,
|
||||
This is not checked in the view becouse the list of documents is inmutable
|
||||
|
||||
"""
|
||||
if not data['devices'] == OrderedSet():
|
||||
return
|
||||
|
||||
documents = []
|
||||
for doc in data['documents']:
|
||||
actions = copy.copy(doc.actions)
|
||||
actions.reverse()
|
||||
for ac in actions:
|
||||
if ac == data['action']:
|
||||
# data['action'] is a Trade action, if this is the first action
|
||||
# to find mean that this document don't have a confirmation
|
||||
break
|
||||
|
||||
if ac.t == 'Revoke' and ac.user == g.user:
|
||||
# this doc is confirmation jet
|
||||
break
|
||||
|
||||
if ac.t == Confirm.t and ac.user == g.user:
|
||||
documents.append(doc)
|
||||
break
|
||||
|
||||
if not documents:
|
||||
txt = 'No there are documents to revoke'
|
||||
raise ValidationError(txt)
|
||||
|
||||
|
||||
class ConfirmDocument(ActionWithMultipleDocuments):
|
||||
__doc__ = m.Confirm.__doc__
|
||||
action = NestedOn('Action', only_query='id')
|
||||
|
||||
@validates_schema
|
||||
def validate_documents(self, data):
|
||||
"""If there are one device than have one confirmation,
|
||||
then remove the list this device of the list of devices of this action
|
||||
"""
|
||||
# import pdb; pdb.set_trace()
|
||||
if data['documents'] == OrderedSet():
|
||||
return
|
||||
|
||||
for doc in data['documents']:
|
||||
if not doc.lot.trade:
|
||||
return
|
||||
|
||||
data['action'] = doc.lot.trade
|
||||
|
||||
if not doc.actions:
|
||||
continue
|
||||
|
||||
if not doc.trading == 'Need Confirmation':
|
||||
txt = 'No there are documents to confirm'
|
||||
raise ValidationError(txt)
|
||||
|
||||
|
||||
class RevokeDocument(ActionWithMultipleDocuments):
|
||||
__doc__ = m.RevokeDocument.__doc__
|
||||
action = NestedOn('Action', only_query='id')
|
||||
|
||||
@validates_schema
|
||||
def validate_documents(self, data):
|
||||
"""Check if there are or no one before confirmation,
|
||||
This is not checked in the view becouse the list of documents is inmutable
|
||||
|
||||
"""
|
||||
# import pdb; pdb.set_trace()
|
||||
if data['documents'] == OrderedSet():
|
||||
return
|
||||
|
||||
for doc in data['documents']:
|
||||
if not doc.lot.trade:
|
||||
return
|
||||
|
||||
data['action'] = doc.lot.trade
|
||||
|
||||
if not doc.actions:
|
||||
continue
|
||||
|
||||
if not doc.trading in ['Document Confirmed', 'Confirm']:
|
||||
txt = 'No there are documents to revoke'
|
||||
raise ValidationError(txt)
|
||||
|
||||
|
||||
class ConfirmRevokeDocument(ActionWithMultipleDocuments):
|
||||
__doc__ = m.ConfirmRevoke.__doc__
|
||||
action = NestedOn('Action', only_query='id')
|
||||
|
||||
@validates_schema
|
||||
def validate_documents(self, data):
|
||||
"""Check if there are or no one before confirmation,
|
||||
This is not checked in the view becouse the list of documents is inmutable
|
||||
|
||||
"""
|
||||
if data['documents'] == OrderedSet():
|
||||
return
|
||||
|
||||
for doc in data['documents']:
|
||||
if not doc.lot.trade:
|
||||
return
|
||||
|
||||
if not doc.actions:
|
||||
continue
|
||||
|
||||
|
||||
if not doc.trading == 'Revoke':
|
||||
txt = 'No there are documents with revoke for confirm'
|
||||
raise ValidationError(txt)
|
||||
|
||||
data['action'] = doc.actions[-1]
|
||||
|
||||
|
||||
class ConfirmRevoke(ActionWithMultipleDevices):
|
||||
__doc__ = m.ConfirmRevoke.__doc__
|
||||
|
@ -489,17 +619,62 @@ class ConfirmRevoke(ActionWithMultipleDevices):
|
|||
|
||||
@validates_schema
|
||||
def validate_revoke(self, data: dict):
|
||||
# import pdb; pdb.set_trace()
|
||||
for dev in data['devices']:
|
||||
# if device not exist in the Trade, then this query is wrong
|
||||
if not dev in data['action'].devices:
|
||||
txt = "Device {} not exist in the revoke action".format(dev.devicehub_id)
|
||||
txt = "Device {} not exist in the trade".format(dev.devicehub_id)
|
||||
raise ValidationError(txt)
|
||||
|
||||
for doc in data.get('documents', []):
|
||||
# if document not exist in the Trade, then this query is wrong
|
||||
if not doc in data['action'].documents:
|
||||
txt = "Document {} not exist in the trade".format(doc.file_name)
|
||||
raise ValidationError(txt)
|
||||
|
||||
@validates_schema
|
||||
def validate_docs(self, data):
|
||||
"""Check if there are or no one before confirmation,
|
||||
This is not checked in the view becouse the list of documents is inmutable
|
||||
|
||||
"""
|
||||
if not data['devices'] == OrderedSet():
|
||||
return
|
||||
|
||||
documents = []
|
||||
for doc in data['documents']:
|
||||
actions = copy.copy(doc.actions)
|
||||
actions.reverse()
|
||||
for ac in actions:
|
||||
if ac == data['action']:
|
||||
# If document have the last action the action for confirm
|
||||
documents.append(doc)
|
||||
break
|
||||
|
||||
if ac.t == 'Revoke' and not ac.user == g.user:
|
||||
# If document is revoke before you can Confirm now
|
||||
# and revoke is an action of one other user
|
||||
documents.append(doc)
|
||||
break
|
||||
|
||||
if ac.t == ConfirmRevoke.t and ac.user == g.user:
|
||||
# If document is confirmed we don't need confirmed again
|
||||
break
|
||||
|
||||
if ac.t == Confirm.t:
|
||||
# if onwer of trade confirm again before than this user Confirm the
|
||||
# revoke, then is not possible confirm the revoke
|
||||
#
|
||||
# If g.user confirm the trade before do a ConfirmRevoke
|
||||
# then g.user can not to do the ConfirmRevoke more
|
||||
break
|
||||
|
||||
if not documents:
|
||||
txt = 'No there are documents with revoke for confirm'
|
||||
raise ValidationError(txt)
|
||||
|
||||
|
||||
class Trade(ActionWithMultipleDevices):
|
||||
__doc__ = m.Trade.__doc__
|
||||
document_id = SanitizedStr(validate=Length(max=STR_SIZE), data_key='documentID', required=False)
|
||||
date = DateTime(data_key='date', required=False)
|
||||
price = Float(required=False, data_key='price')
|
||||
user_to_email = SanitizedStr(
|
||||
|
@ -542,7 +717,13 @@ class Trade(ActionWithMultipleDevices):
|
|||
txt = "you need to be the owner of the lot for to do a trade"
|
||||
raise ValidationError(txt)
|
||||
|
||||
for doc in data['lot'].documents:
|
||||
if not doc.owner == g.user:
|
||||
txt = "you need to be the owner of the documents for to do a trade"
|
||||
raise ValidationError(txt)
|
||||
|
||||
data['devices'] = data['lot'].devices
|
||||
data['documents'] = data['lot'].documents
|
||||
|
||||
@validates_schema
|
||||
def validate_user_to_email(self, data: dict):
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import copy
|
||||
|
||||
from flask import g
|
||||
from sqlalchemy.util import OrderedSet
|
||||
from teal.marshmallow import ValidationError
|
||||
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.resources.action.models import Trade, Confirm, ConfirmRevoke, Revoke
|
||||
from ereuse_devicehub.resources.action.models import (Trade, Confirm, ConfirmRevoke,
|
||||
Revoke, RevokeDocument, ConfirmDocument,
|
||||
ConfirmRevokeDocument)
|
||||
from ereuse_devicehub.resources.user.models import User
|
||||
from ereuse_devicehub.resources.lot.views import delete_from_trade
|
||||
|
||||
|
@ -16,11 +16,11 @@ class TradeView():
|
|||
request_post = {
|
||||
'type': 'Trade',
|
||||
'devices': [device_id],
|
||||
'documents': [document_id],
|
||||
'userFrom': user2.email,
|
||||
'userTo': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirm': True,
|
||||
}
|
||||
|
@ -51,10 +51,17 @@ class TradeView():
|
|||
# if the confirmation is mandatory, do automatic confirmation only for
|
||||
# owner of the lot
|
||||
if self.trade.confirm:
|
||||
confirm = Confirm(user=g.user,
|
||||
action=self.trade,
|
||||
devices=self.trade.devices)
|
||||
db.session.add(confirm)
|
||||
if self.trade.devices:
|
||||
confirm_devs = Confirm(user=g.user,
|
||||
action=self.trade,
|
||||
devices=self.trade.devices)
|
||||
db.session.add(confirm_devs)
|
||||
|
||||
if self.trade.documents:
|
||||
confirm_docs = ConfirmDocument(user=g.user,
|
||||
action=self.trade,
|
||||
documents=self.trade.documents)
|
||||
db.session.add(confirm_docs)
|
||||
return
|
||||
|
||||
# check than the user than want to do the action is one of the users
|
||||
|
@ -173,7 +180,6 @@ class ConfirmView(ConfirmMixin):
|
|||
"""If there are one device than have one confirmation,
|
||||
then remove the list this device of the list of devices of this action
|
||||
"""
|
||||
# import pdb; pdb.set_trace()
|
||||
real_devices = []
|
||||
for dev in data['devices']:
|
||||
ac = dev.last_action_trading
|
||||
|
@ -261,3 +267,111 @@ class ConfirmRevokeView(ConfirmMixin):
|
|||
dev.reset_owner()
|
||||
|
||||
trade.lot.devices.difference_update(devices)
|
||||
|
||||
|
||||
class ConfirmDocumentMixin():
|
||||
"""
|
||||
Very Important:
|
||||
==============
|
||||
All of this Views than inherit of this class is executed for users
|
||||
than is not owner of the Trade action.
|
||||
|
||||
The owner of Trade action executed this actions of confirm and revoke from the
|
||||
lot
|
||||
|
||||
"""
|
||||
|
||||
Model = None
|
||||
|
||||
def __init__(self, data, resource_def, schema):
|
||||
# import pdb; pdb.set_trace()
|
||||
self.schema = schema
|
||||
a = resource_def.schema.load(data)
|
||||
self.validate(a)
|
||||
if not a['documents']:
|
||||
raise ValidationError('Documents not exist.')
|
||||
self.model = self.Model(**a)
|
||||
|
||||
def post(self):
|
||||
db.session().final_flush()
|
||||
ret = self.schema.jsonify(self.model)
|
||||
ret.status_code = 201
|
||||
db.session.commit()
|
||||
return ret
|
||||
|
||||
|
||||
class ConfirmDocumentView(ConfirmDocumentMixin):
|
||||
"""Handler for manager the Confirmation register from post
|
||||
|
||||
request_confirm = {
|
||||
'type': 'Confirm',
|
||||
'action': trade.id,
|
||||
'documents': [document_id],
|
||||
}
|
||||
"""
|
||||
|
||||
Model = ConfirmDocument
|
||||
|
||||
def validate(self, data):
|
||||
"""If there are one device than have one confirmation,
|
||||
then remove the list this device of the list of devices of this action
|
||||
"""
|
||||
for doc in data['documents']:
|
||||
ac = doc.trading
|
||||
if not doc.trading in ['Confirm', 'Need Confirmation']:
|
||||
txt = 'Some of documents do not have enough to confirm for to do a Doble Confirmation'
|
||||
ValidationError(txt)
|
||||
### End check ###
|
||||
|
||||
|
||||
class RevokeDocumentView(ConfirmDocumentMixin):
|
||||
"""Handler for manager the Revoke register from post
|
||||
|
||||
request_revoke = {
|
||||
'type': 'Revoke',
|
||||
'action': trade.id,
|
||||
'documents': [document_id],
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
Model = RevokeDocument
|
||||
|
||||
def validate(self, data):
|
||||
"""All devices need to have the status of DoubleConfirmation."""
|
||||
|
||||
### check ###
|
||||
if not data['documents']:
|
||||
raise ValidationError('Documents not exist.')
|
||||
|
||||
for doc in data['documents']:
|
||||
if not doc.trading in ['Document Confirmed', 'Confirm']:
|
||||
txt = 'Some of documents do not have enough to confirm for to do a revoke'
|
||||
ValidationError(txt)
|
||||
### End check ###
|
||||
|
||||
|
||||
class ConfirmRevokeDocumentView(ConfirmDocumentMixin):
|
||||
"""Handler for manager the Confirmation register from post
|
||||
|
||||
request_confirm_revoke = {
|
||||
'type': 'ConfirmRevoke',
|
||||
'action': action_revoke.id,
|
||||
'documents': [document_id],
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
Model = ConfirmRevokeDocument
|
||||
|
||||
def validate(self, data):
|
||||
"""All devices need to have the status of revoke."""
|
||||
|
||||
if not data['action'].type == 'RevokeDocument':
|
||||
txt = 'Error: this action is not a revoke action'
|
||||
ValidationError(txt)
|
||||
|
||||
for doc in data['documents']:
|
||||
if not doc.trading == 'Revoke':
|
||||
txt = 'Some of documents do not have revoke to confirm'
|
||||
ValidationError(txt)
|
||||
|
|
|
@ -202,6 +202,19 @@ class ActionView(View):
|
|||
confirm_revoke = trade_view.ConfirmRevokeView(json, resource_def, self.schema)
|
||||
return confirm_revoke.post()
|
||||
|
||||
if json['type'] == 'RevokeDocument':
|
||||
revoke = trade_view.RevokeDocumentView(json, resource_def, self.schema)
|
||||
return revoke.post()
|
||||
|
||||
if json['type'] == 'ConfirmDocument':
|
||||
confirm = trade_view.ConfirmDocumentView(json, resource_def, self.schema)
|
||||
return confirm.post()
|
||||
|
||||
if json['type'] == 'ConfirmRevokeDocument':
|
||||
confirm_revoke = trade_view.ConfirmRevokeDocumentView(json, resource_def, self.schema)
|
||||
return confirm_revoke.post()
|
||||
|
||||
# import pdb; pdb.set_trace()
|
||||
a = resource_def.schema.load(json)
|
||||
Model = db.Model._decl_class_registry.data[json['type']]()
|
||||
action = Model(**a)
|
||||
|
|
|
@ -625,11 +625,11 @@ class Computer(Device):
|
|||
It is a subset of the Linux definition of DMI / DMI decode.
|
||||
"""
|
||||
amount = Column(Integer, check_range('amount', min=0, max=100), default=0)
|
||||
owner_id = db.Column(UUID(as_uuid=True),
|
||||
db.ForeignKey(User.id),
|
||||
nullable=False,
|
||||
default=lambda: g.user.id)
|
||||
author = db.relationship(User, primaryjoin=owner_id == User.id)
|
||||
# owner_id = db.Column(UUID(as_uuid=True),
|
||||
# db.ForeignKey(User.id),
|
||||
# nullable=False,
|
||||
# default=lambda: g.user.id)
|
||||
# author = db.relationship(User, primaryjoin=owner_id == User.id)
|
||||
transfer_state = db.Column(IntEnum(TransferState), default=TransferState.Initial, nullable=False)
|
||||
transfer_state.comment = TransferState.__doc__
|
||||
receiver_id = db.Column(UUID(as_uuid=True),
|
||||
|
|
|
@ -50,7 +50,7 @@ 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 = EnumField(states.Trading, dump_only=True, description=m.Device.trading.__doc__)
|
||||
trading = SanitizedStr(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__)
|
||||
|
|
|
@ -5,6 +5,7 @@ from ereuse_devicehub.marshmallow import NestedOn
|
|||
from ereuse_devicehub.resources.deliverynote import schemas as s_deliverynote
|
||||
from ereuse_devicehub.resources.device import schemas as s_device
|
||||
from ereuse_devicehub.resources.action import schemas as s_action
|
||||
from ereuse_devicehub.resources.tradedocument import schemas as s_document
|
||||
from ereuse_devicehub.resources.enums import TransferState
|
||||
from ereuse_devicehub.resources.lot import models as m
|
||||
from ereuse_devicehub.resources.models import STR_SIZE
|
||||
|
@ -27,4 +28,5 @@ class Lot(Thing):
|
|||
transfer_state = EnumField(TransferState, description=m.Lot.transfer_state.comment)
|
||||
receiver_address = SanitizedStr(validate=f.validate.Length(max=42))
|
||||
deliverynote = NestedOn(s_deliverynote.Deliverynote, dump_only=True)
|
||||
documents = NestedOn('TradeDocument', many=True, dump_only=True)
|
||||
trade = NestedOn(s_action.Trade, dump_only=True)
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
from teal.resource import Converters, Resource
|
||||
|
||||
from ereuse_devicehub.resources.tradedocument import schemas
|
||||
from ereuse_devicehub.resources.tradedocument.views import TradeDocumentView
|
||||
|
||||
class TradeDocumentDef(Resource):
|
||||
SCHEMA = schemas.TradeDocument
|
||||
VIEW = TradeDocumentView
|
||||
AUTH = True
|
||||
ID_CONVERTER = Converters.string
|
|
@ -0,0 +1,141 @@
|
|||
import copy
|
||||
from citext import CIText
|
||||
from flask import g
|
||||
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.resources.user.models import User
|
||||
from sortedcontainers import SortedSet
|
||||
from ereuse_devicehub.resources.models import Thing
|
||||
|
||||
from sqlalchemy import BigInteger, Column, Sequence
|
||||
from sqlalchemy.orm import backref
|
||||
from teal.db import CASCADE_OWN, URL
|
||||
|
||||
from ereuse_devicehub.resources.enums import Severity
|
||||
|
||||
|
||||
_sorted_documents = {
|
||||
'order_by': lambda: TradeDocument.created,
|
||||
'collection_class': SortedSet
|
||||
}
|
||||
|
||||
class TradeDocument(Thing):
|
||||
"""This represent a document involved in a trade action.
|
||||
Every document is added to a lot.
|
||||
When this lot is converted in one trade, the action trade is added to the document
|
||||
and the action trade need to be confirmed for the both users of the trade.
|
||||
This confirmation can be revoked and this revoked need to be ConfirmRevoke for have
|
||||
some efect.
|
||||
|
||||
This documents can be invoices or list of devices or certificates of erasure of
|
||||
one disk.
|
||||
|
||||
Like a Devices one document have actions and is possible add or delete of one lot
|
||||
if this lot don't have a trade
|
||||
|
||||
The document is saved in the database
|
||||
|
||||
"""
|
||||
|
||||
id = Column(BigInteger, Sequence('device_seq'), primary_key=True)
|
||||
id.comment = """The identifier of the device for this database. Used only
|
||||
internally for software; users should not use this.
|
||||
"""
|
||||
# type = Column(Unicode(STR_SM_SIZE), nullable=False)
|
||||
date = Column(db.DateTime)
|
||||
date.comment = """The date of document, some documents need to have one date
|
||||
"""
|
||||
id_document = Column(CIText())
|
||||
id_document.comment = """The id of one document like invoice so they can be linked."""
|
||||
description = Column(db.CIText())
|
||||
description.comment = """A description of document."""
|
||||
owner_id = db.Column(UUID(as_uuid=True),
|
||||
db.ForeignKey(User.id),
|
||||
nullable=False,
|
||||
default=lambda: g.user.id)
|
||||
owner = db.relationship(User, primaryjoin=owner_id == User.id)
|
||||
lot_id = db.Column(UUID(as_uuid=True),
|
||||
db.ForeignKey('lot.id'),
|
||||
nullable=False)
|
||||
lot = db.relationship('Lot',
|
||||
backref=backref('documents',
|
||||
lazy=True,
|
||||
cascade=CASCADE_OWN,
|
||||
**_sorted_documents),
|
||||
primaryjoin='TradeDocument.lot_id == Lot.id')
|
||||
lot.comment = """Lot to which the document is associated"""
|
||||
file_name = Column(db.CIText())
|
||||
file_name.comment = """This is the name of the file when user up the document."""
|
||||
file_hash = Column(db.CIText())
|
||||
file_hash.comment = """This is the hash of the file produced from frontend."""
|
||||
url = db.Column(URL())
|
||||
url.comment = """This is the url where resides the document."""
|
||||
|
||||
__table_args__ = (
|
||||
db.Index('document_id', id, postgresql_using='hash'),
|
||||
# db.Index('type_doc', type, postgresql_using='hash')
|
||||
)
|
||||
|
||||
@property
|
||||
def actions(self) -> list:
|
||||
"""All the actions where the device participated, including:
|
||||
|
||||
1. Actions performed directly to the device.
|
||||
2. Actions performed to a component.
|
||||
3. Actions performed to a parent device.
|
||||
|
||||
Actions are returned by descending ``created`` time.
|
||||
"""
|
||||
return sorted(self.actions_docs, key=lambda x: x.created)
|
||||
|
||||
@property
|
||||
def trading(self):
|
||||
"""The trading state, or None if no Trade action has
|
||||
ever been performed to this device. This extract the posibilities for to do"""
|
||||
|
||||
confirm = 'Confirm'
|
||||
need_confirm = 'Need Confirmation'
|
||||
double_confirm = 'Document Confirmed'
|
||||
revoke = 'Revoke'
|
||||
revoke_pending = 'Revoke Pending'
|
||||
confirm_revoke = 'Document Revoked'
|
||||
|
||||
if not self.actions:
|
||||
return
|
||||
|
||||
ac = self.actions[-1]
|
||||
|
||||
if ac.type == 'ConfirmRevokeDocument':
|
||||
# can to do revoke_confirmed
|
||||
return confirm_revoke
|
||||
|
||||
if ac.type == 'RevokeDocument':
|
||||
if ac.user == g.user:
|
||||
# can todo revoke_pending
|
||||
return revoke_pending
|
||||
else:
|
||||
# can to do confirm_revoke
|
||||
return revoke
|
||||
|
||||
if ac.type == 'ConfirmDocument':
|
||||
if ac.user == self.owner:
|
||||
if self.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
|
||||
|
||||
def _warning_actions(self, actions):
|
||||
return sorted(ev for ev in actions if ev.severity >= Severity.Warning)
|
||||
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.id < other.id
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '{0.file_name}'.format(self)
|
|
@ -0,0 +1,31 @@
|
|||
from marshmallow.fields import DateTime, Integer, validate
|
||||
from teal.marshmallow import SanitizedStr, URL
|
||||
# from marshmallow import ValidationError, validates_schema
|
||||
|
||||
from ereuse_devicehub.marshmallow import NestedOn
|
||||
from ereuse_devicehub.resources.schemas import Thing
|
||||
from ereuse_devicehub.resources.tradedocument import models as m
|
||||
# from ereuse_devicehub.resources.lot import schemas as s_lot
|
||||
|
||||
|
||||
class TradeDocument(Thing):
|
||||
__doc__ = m.TradeDocument.__doc__
|
||||
id = Integer(description=m.TradeDocument.id.comment, dump_only=True)
|
||||
date = DateTime(required=False, description=m.TradeDocument.date.comment)
|
||||
id_document = SanitizedStr(data_key='documentId',
|
||||
default='',
|
||||
description=m.TradeDocument.id_document.comment)
|
||||
description = SanitizedStr(default='',
|
||||
description=m.TradeDocument.description.comment,
|
||||
validate=validate.Length(max=500))
|
||||
file_name = SanitizedStr(data_key='filename',
|
||||
default='',
|
||||
description=m.TradeDocument.file_name.comment,
|
||||
validate=validate.Length(max=100))
|
||||
file_hash = SanitizedStr(data_key='hash',
|
||||
default='',
|
||||
description=m.TradeDocument.file_hash.comment,
|
||||
validate=validate.Length(max=64))
|
||||
url = URL(description=m.TradeDocument.url.comment)
|
||||
lot = NestedOn('Lot', only_query='id', description=m.TradeDocument.lot.__doc__)
|
||||
trading = SanitizedStr(dump_only=True, description='')
|
|
@ -0,0 +1,47 @@
|
|||
import os
|
||||
import time
|
||||
from datetime import datetime
|
||||
from flask import current_app as app, request, g, Response
|
||||
from teal.resource import View
|
||||
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.resources.tradedocument.models import TradeDocument
|
||||
from ereuse_devicehub.resources.action.models import ConfirmDocument
|
||||
from ereuse_devicehub.resources.hash_reports import ReportHash
|
||||
|
||||
|
||||
class TradeDocumentView(View):
|
||||
|
||||
def one(self, id: str):
|
||||
doc = TradeDocument.query.filter_by(id=id, owner=g.user).one()
|
||||
return self.schema.jsonify(doc)
|
||||
|
||||
def post(self):
|
||||
"""Add one document."""
|
||||
|
||||
data = request.get_json(validate=True)
|
||||
hash3 = data['file_hash']
|
||||
db_hash = ReportHash(hash3=hash3)
|
||||
db.session.add(db_hash)
|
||||
|
||||
doc = TradeDocument(**data)
|
||||
trade = doc.lot.trade
|
||||
if trade:
|
||||
trade.documents.add(doc)
|
||||
confirm = ConfirmDocument(action=trade,
|
||||
user=g.user,
|
||||
devices=set(),
|
||||
documents={doc})
|
||||
db.session.add(confirm)
|
||||
db.session.add(doc)
|
||||
db.session().final_flush()
|
||||
ret = self.schema.jsonify(doc)
|
||||
ret.status_code = 201
|
||||
db.session.commit()
|
||||
return ret
|
||||
|
||||
def delete(self, id):
|
||||
doc = TradeDocument.query.filter_by(id=id, owner=g.user).one()
|
||||
db.session.delete(doc)
|
||||
db.session.commit()
|
||||
return Response(status=204)
|
|
@ -59,6 +59,13 @@ class User(Thing):
|
|||
"""The individual associated for this database, or None."""
|
||||
return next(iter(self.individuals), None)
|
||||
|
||||
@property
|
||||
def code(self):
|
||||
"""Code of phantoms accounts"""
|
||||
if not self.phantom:
|
||||
return
|
||||
return self.email.split('@')[0].split('_')[1]
|
||||
|
||||
|
||||
class UserInventory(db.Model):
|
||||
"""Relationship between users and their inventories."""
|
||||
|
|
|
@ -23,6 +23,7 @@ class User(Thing):
|
|||
description='Use this token in an Authorization header to access the app.'
|
||||
'The token can change overtime.')
|
||||
inventories = NestedOn(Inventory, many=True, dump_only=True)
|
||||
code = String(dump_only=True, description='Code of inactive accounts')
|
||||
|
||||
def __init__(self,
|
||||
only=None,
|
||||
|
|
|
@ -35,6 +35,7 @@ class TestConfig(DevicehubConfig):
|
|||
TMP_SNAPSHOTS = '/tmp/snapshots'
|
||||
TMP_LIVES = '/tmp/lives'
|
||||
EMAIL_ADMIN = 'foo@foo.com'
|
||||
PATH_DOCUMENTS_STORAGE = '/tmp/trade_documents'
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
|
|
|
@ -765,7 +765,6 @@ 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_offer_without_to(user: UserClient):
|
||||
|
@ -789,7 +788,6 @@ def test_offer_without_to(user: UserClient):
|
|||
'userFromEmail': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirms': False,
|
||||
'code': 'MAX'
|
||||
|
@ -817,7 +815,6 @@ def test_offer_without_to(user: UserClient):
|
|||
'userFromEmail': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirms': False,
|
||||
'code': 'MAX'
|
||||
|
@ -840,7 +837,6 @@ def test_offer_without_to(user: UserClient):
|
|||
'userFromEmail': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot2.id,
|
||||
'confirms': False,
|
||||
'code': 'MAX'
|
||||
|
@ -871,7 +867,6 @@ def test_offer_without_from(user: UserClient, user2: UserClient):
|
|||
'userToEmail': user2.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot.id,
|
||||
'confirms': False,
|
||||
'code': 'MAX'
|
||||
|
@ -916,7 +911,6 @@ def test_offer_without_users(user: UserClient):
|
|||
'devices': [device.id],
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot.id,
|
||||
'confirms': False,
|
||||
'code': 'MAX'
|
||||
|
@ -950,7 +944,6 @@ def test_offer(user: UserClient):
|
|||
'userToEmail': user2.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot.id,
|
||||
'confirms': True,
|
||||
}
|
||||
|
@ -977,7 +970,6 @@ def test_offer_without_devices(user: UserClient):
|
|||
'userToEmail': user2.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirms': True,
|
||||
}
|
||||
|
@ -1036,7 +1028,6 @@ def test_erase_physical():
|
|||
db.session.add(erasure)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
@pytest.mark.mvp
|
||||
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||
def test_endpoint_confirm(user: UserClient, user2: UserClient):
|
||||
|
@ -1056,7 +1047,6 @@ def test_endpoint_confirm(user: UserClient, user2: UserClient):
|
|||
'userToEmail': user2.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirms': True,
|
||||
}
|
||||
|
@ -1097,7 +1087,6 @@ def test_confirm_revoke(user: UserClient, user2: UserClient):
|
|||
'userToEmail': user2.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirms': True,
|
||||
}
|
||||
|
@ -1172,7 +1161,6 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient):
|
|||
'userToEmail': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirms': True,
|
||||
}
|
||||
|
@ -1362,7 +1350,6 @@ def test_confirmRevoke(user: UserClient, user2: UserClient):
|
|||
'userToEmail': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirms': True,
|
||||
}
|
||||
|
@ -1477,7 +1464,6 @@ def test_trade_case1(user: UserClient, user2: UserClient):
|
|||
'userToEmail': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirms': True,
|
||||
}
|
||||
|
@ -1538,7 +1524,6 @@ def test_trade_case2(user: UserClient, user2: UserClient):
|
|||
'userToEmail': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirms': True,
|
||||
}
|
||||
|
@ -1603,7 +1588,6 @@ def test_trade_case3(user: UserClient, user2: UserClient):
|
|||
'userToEmail': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirms': True,
|
||||
}
|
||||
|
@ -1661,7 +1645,6 @@ def test_trade_case4(user: UserClient, user2: UserClient):
|
|||
'userToEmail': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirms': True,
|
||||
}
|
||||
|
@ -1727,7 +1710,6 @@ def test_trade_case5(user: UserClient, user2: UserClient):
|
|||
'userToEmail': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirms': True,
|
||||
}
|
||||
|
@ -1793,7 +1775,6 @@ def test_trade_case6(user: UserClient, user2: UserClient):
|
|||
'userToEmail': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirms': True,
|
||||
}
|
||||
|
@ -1861,7 +1842,6 @@ def test_trade_case7(user: UserClient, user2: UserClient):
|
|||
'userToEmail': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirms': True,
|
||||
}
|
||||
|
@ -1927,7 +1907,6 @@ def test_trade_case8(user: UserClient, user2: UserClient):
|
|||
'userToEmail': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirms': True,
|
||||
}
|
||||
|
@ -2000,7 +1979,6 @@ def test_trade_case9(user: UserClient, user2: UserClient):
|
|||
'userToEmail': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirms': True,
|
||||
}
|
||||
|
@ -2081,7 +2059,6 @@ def test_trade_case10(user: UserClient, user2: UserClient):
|
|||
'userToEmail': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirms': True,
|
||||
}
|
||||
|
@ -2166,7 +2143,6 @@ def test_trade_case11(user: UserClient, user2: UserClient):
|
|||
'userToEmail': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirms': True,
|
||||
}
|
||||
|
@ -2235,7 +2211,6 @@ def test_trade_case12(user: UserClient, user2: UserClient):
|
|||
'userToEmail': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirms': True,
|
||||
}
|
||||
|
@ -2310,7 +2285,6 @@ def test_trade_case13(user: UserClient, user2: UserClient):
|
|||
'userToEmail': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirms': True,
|
||||
}
|
||||
|
@ -2385,7 +2359,6 @@ def test_trade_case14(user: UserClient, user2: UserClient):
|
|||
'userToEmail': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirms': True,
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ def test_api_docs(client: Client):
|
|||
'/metrics/',
|
||||
'/tags/',
|
||||
'/tags/{tag_id}/device/{device_id}',
|
||||
'/trade-documents/',
|
||||
'/users/',
|
||||
'/users/login/',
|
||||
'/users/logout/',
|
||||
|
@ -121,4 +122,4 @@ def test_api_docs(client: Client):
|
|||
'scheme': 'basic',
|
||||
'name': 'Authorization'
|
||||
}
|
||||
assert len(docs['definitions']) == 121
|
||||
assert len(docs['definitions']) == 125
|
||||
|
|
Reference in New Issue