Merge pull request #163 from eReuse/feature/container
Feature/container
This commit is contained in:
commit
baa5fccda0
|
@ -0,0 +1,47 @@
|
||||||
|
"""adding weight to tradedocuments
|
||||||
|
|
||||||
|
Revision ID: 3ac2bc1897ce
|
||||||
|
Revises: 3a3601ac8224
|
||||||
|
Create Date: 2021-08-03 16:28:38.719686
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import context
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '3ac2bc1897ce'
|
||||||
|
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.add_column("trade_document", sa.Column("weight", sa.Float(decimal_return_scale=2), nullable=True), schema=f'{get_inv()}')
|
||||||
|
|
||||||
|
# DataWipeDocument table
|
||||||
|
op.create_table('move_on_document',
|
||||||
|
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||||
|
sa.Column("weight", sa.Float(decimal_return_scale=2), nullable=True),
|
||||||
|
sa.Column('container_from_id', sa.BigInteger(), nullable=False),
|
||||||
|
sa.Column('container_to_id', sa.BigInteger(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['container_from_id'], [f'{get_inv()}.trade_document.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['container_to_id'], [f'{get_inv()}.trade_document.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['id'], [f'{get_inv()}.action.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
schema=f'{get_inv()}'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
op.drop_column('trade_document', 'weight', schema=f'{get_inv()}')
|
||||||
|
op.drop_table('move_on_document', schema=f'{get_inv()}')
|
|
@ -313,3 +313,8 @@ class MigrateToDef(ActionDef):
|
||||||
class MigrateFromDef(ActionDef):
|
class MigrateFromDef(ActionDef):
|
||||||
VIEW = None
|
VIEW = None
|
||||||
SCHEMA = schemas.MigrateFrom
|
SCHEMA = schemas.MigrateFrom
|
||||||
|
|
||||||
|
|
||||||
|
class MoveOnDocumentDef(ActionDef):
|
||||||
|
VIEW = None
|
||||||
|
SCHEMA = schemas.MoveOnDocument
|
||||||
|
|
|
@ -62,6 +62,15 @@ _sorted_actions = {
|
||||||
'order_by': lambda: Action.end_time,
|
'order_by': lambda: Action.end_time,
|
||||||
'collection_class': SortedSet
|
'collection_class': SortedSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def sorted_actions_by(data):
|
||||||
|
return {
|
||||||
|
'order_by': lambda: data,
|
||||||
|
'collection_class': SortedSet
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
"""For db.backref, return the actions sorted by end_time."""
|
"""For db.backref, return the actions sorted by end_time."""
|
||||||
|
|
||||||
|
|
||||||
|
@ -1642,6 +1651,35 @@ class MakeAvailable(ActionWithMultipleDevices):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MoveOnDocument(JoinedTableMixin, ActionWithMultipleTradeDocuments):
|
||||||
|
"""Action than certify one movement of some indescriptible material of
|
||||||
|
one container to an other."""
|
||||||
|
|
||||||
|
weight = db.Column(db.Float(nullable=True))
|
||||||
|
weight.comment = """Weight than go to recycling"""
|
||||||
|
container_from_id = db.Column(
|
||||||
|
db.BigInteger,
|
||||||
|
db.ForeignKey('trade_document.id'),
|
||||||
|
nullable=False
|
||||||
|
)
|
||||||
|
container_from = db.relationship(
|
||||||
|
'TradeDocument',
|
||||||
|
primaryjoin='MoveOnDocument.container_from_id == TradeDocument.id',
|
||||||
|
)
|
||||||
|
container_from_id.comment = """This is the trade document used as container in a incoming lot"""
|
||||||
|
|
||||||
|
container_to_id = db.Column(
|
||||||
|
db.BigInteger,
|
||||||
|
db.ForeignKey('trade_document.id'),
|
||||||
|
nullable=False
|
||||||
|
)
|
||||||
|
container_to = db.relationship(
|
||||||
|
'TradeDocument',
|
||||||
|
primaryjoin='MoveOnDocument.container_to_id == TradeDocument.id',
|
||||||
|
)
|
||||||
|
container_to_id.comment = """This is the trade document used as container in a outgoing lot"""
|
||||||
|
|
||||||
|
|
||||||
class Migrate(JoinedTableMixin, ActionWithMultipleDevices):
|
class Migrate(JoinedTableMixin, ActionWithMultipleDevices):
|
||||||
"""Moves the devices to a new database/inventory. Devices cannot be
|
"""Moves the devices to a new database/inventory. Devices cannot be
|
||||||
modified anymore at the previous database.
|
modified anymore at the previous database.
|
||||||
|
|
|
@ -2,7 +2,7 @@ import copy
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from dateutil.tz import tzutc
|
from dateutil.tz import tzutc
|
||||||
from flask import current_app as app, g
|
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, pre_load, post_load
|
||||||
from marshmallow.fields import Boolean, DateTime, Decimal, Float, Integer, Nested, String, \
|
from marshmallow.fields import Boolean, DateTime, Decimal, Float, Integer, Nested, String, \
|
||||||
TimeDelta, UUID
|
TimeDelta, UUID
|
||||||
from marshmallow.validate import Length, OneOf, Range
|
from marshmallow.validate import Length, OneOf, Range
|
||||||
|
@ -25,6 +25,7 @@ from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
|
||||||
from ereuse_devicehub.resources.schemas import Thing
|
from ereuse_devicehub.resources.schemas import Thing
|
||||||
from ereuse_devicehub.resources.user import schemas as s_user
|
from ereuse_devicehub.resources.user import schemas as s_user
|
||||||
from ereuse_devicehub.resources.user.models import User
|
from ereuse_devicehub.resources.user.models import User
|
||||||
|
from ereuse_devicehub.resources.tradedocument.models import TradeDocument
|
||||||
|
|
||||||
|
|
||||||
class Action(Thing):
|
class Action(Thing):
|
||||||
|
@ -840,3 +841,31 @@ class MigrateTo(Migrate):
|
||||||
|
|
||||||
class MigrateFrom(Migrate):
|
class MigrateFrom(Migrate):
|
||||||
__doc__ = m.MigrateFrom.__doc__
|
__doc__ = m.MigrateFrom.__doc__
|
||||||
|
|
||||||
|
|
||||||
|
class MoveOnDocument(Action):
|
||||||
|
__doc__ = m.MoveOnDocument.__doc__
|
||||||
|
weight = Integer()
|
||||||
|
container_from = NestedOn('TradeDocument', only_query='id')
|
||||||
|
container_to = NestedOn('TradeDocument', only_query='id')
|
||||||
|
|
||||||
|
@pre_load
|
||||||
|
def extract_container(self, data):
|
||||||
|
id_hash = data['container_to']
|
||||||
|
docs = TradeDocument.query.filter_by(owner=g.user, file_hash=id_hash).all()
|
||||||
|
if len(docs) > 1:
|
||||||
|
txt = 'This document it is associated in more than one lot'
|
||||||
|
raise ValidationError(txt)
|
||||||
|
|
||||||
|
if len(docs) < 1:
|
||||||
|
txt = 'This document not exist'
|
||||||
|
raise ValidationError(txt)
|
||||||
|
data['container_to'] = docs[0].id
|
||||||
|
|
||||||
|
@post_load
|
||||||
|
def adding_documents(self, data):
|
||||||
|
"""Adding action in the 2 TradeDocuments"""
|
||||||
|
docs = OrderedSet()
|
||||||
|
docs.add(data['container_to'])
|
||||||
|
docs.add(data['container_from'])
|
||||||
|
data['documents'] = docs
|
||||||
|
|
|
@ -434,3 +434,4 @@ class DocumentDef(Resource):
|
||||||
auth=app.auth)
|
auth=app.auth)
|
||||||
wbconf_view = app.auth.requires_auth(wbconf_view)
|
wbconf_view = app.auth.requires_auth(wbconf_view)
|
||||||
self.add_url_rule('/wbconf/<string:wbtype>', view_func=wbconf_view, methods=get)
|
self.add_url_rule('/wbconf/<string:wbtype>', view_func=wbconf_view, methods=get)
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,23 @@
|
||||||
from citext import CIText
|
|
||||||
from flask import g
|
from flask import g
|
||||||
|
from citext import CIText
|
||||||
|
from sortedcontainers import SortedSet
|
||||||
from sqlalchemy import BigInteger, Column, Sequence, Unicode, Boolean, ForeignKey
|
from sqlalchemy import BigInteger, Column, Sequence, Unicode, Boolean, ForeignKey
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
from teal.db import URL
|
from sqlalchemy.orm import backref
|
||||||
|
from teal.db import CASCADE_OWN, URL
|
||||||
|
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.resources.user.models import User
|
from ereuse_devicehub.resources.user.models import User
|
||||||
from ereuse_devicehub.resources.models import Thing, STR_SM_SIZE
|
from ereuse_devicehub.resources.models import Thing, STR_SM_SIZE
|
||||||
|
|
||||||
|
|
||||||
|
_sorted_documents = {
|
||||||
|
'order_by': lambda: Document.created,
|
||||||
|
'collection_class': SortedSet
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Document(Thing):
|
class Document(Thing):
|
||||||
"""This represent a generic document."""
|
"""This represent a generic document."""
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
from marshmallow.fields import DateTime, Integer, validate, Boolean
|
from marshmallow.fields import DateTime, Integer, validate, Boolean, Float
|
||||||
from marshmallow import post_load
|
from marshmallow import post_load
|
||||||
|
from marshmallow.validate import Range
|
||||||
from teal.marshmallow import SanitizedStr, URL
|
from teal.marshmallow import SanitizedStr, URL
|
||||||
|
from ereuse_devicehub.marshmallow import NestedOn
|
||||||
from ereuse_devicehub.resources.schemas import Thing
|
from ereuse_devicehub.resources.schemas import Thing
|
||||||
|
from ereuse_devicehub.resources.tradedocument.models import TradeDocument
|
||||||
from ereuse_devicehub.resources.documents import models as m
|
from ereuse_devicehub.resources.documents import models as m
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import copy
|
|
||||||
from citext import CIText
|
from citext import CIText
|
||||||
from flask import g
|
from flask import g
|
||||||
|
|
||||||
|
@ -71,6 +70,8 @@ class TradeDocument(Thing):
|
||||||
file_hash.comment = """This is the hash of the file produced from frontend."""
|
file_hash.comment = """This is the hash of the file produced from frontend."""
|
||||||
url = db.Column(URL())
|
url = db.Column(URL())
|
||||||
url.comment = """This is the url where resides the document."""
|
url.comment = """This is the url where resides the document."""
|
||||||
|
weight = db.Column(db.Float(nullable=True))
|
||||||
|
weight.comment = """This is the weight of one container than this document express."""
|
||||||
|
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
db.Index('document_id', id, postgresql_using='hash'),
|
db.Index('document_id', id, postgresql_using='hash'),
|
||||||
|
@ -100,7 +101,6 @@ class TradeDocument(Thing):
|
||||||
revoke = 'Revoke'
|
revoke = 'Revoke'
|
||||||
revoke_pending = 'Revoke Pending'
|
revoke_pending = 'Revoke Pending'
|
||||||
confirm_revoke = 'Document Revoked'
|
confirm_revoke = 'Document Revoked'
|
||||||
|
|
||||||
if not self.actions:
|
if not self.actions:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -130,9 +130,22 @@ class TradeDocument(Thing):
|
||||||
# can to do revoke
|
# can to do revoke
|
||||||
return double_confirm
|
return double_confirm
|
||||||
|
|
||||||
def _warning_actions(self, actions):
|
@property
|
||||||
return sorted(ev for ev in actions if ev.severity >= Severity.Warning)
|
def total_weight(self):
|
||||||
|
"""Return all weight than this container have."""
|
||||||
|
weight = self.weight or 0
|
||||||
|
for x in self.actions:
|
||||||
|
if not x.type == 'MoveOnDocument' or not x.weight:
|
||||||
|
continue
|
||||||
|
if self == x.container_from:
|
||||||
|
continue
|
||||||
|
weight += x.weight
|
||||||
|
|
||||||
|
return weight
|
||||||
|
|
||||||
|
def _warning_actions(self, actions):
|
||||||
|
"""Show warning actions"""
|
||||||
|
return sorted(ev for ev in actions if ev.severity >= Severity.Warning)
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
return self.id < other.id
|
return self.id < other.id
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from marshmallow.fields import DateTime, Integer, validate
|
from marshmallow.fields import DateTime, Integer, Float, validate
|
||||||
from teal.marshmallow import SanitizedStr, URL
|
from teal.marshmallow import SanitizedStr, URL
|
||||||
# from marshmallow import ValidationError, validates_schema
|
# from marshmallow import ValidationError, validates_schema
|
||||||
|
|
||||||
|
@ -29,3 +29,5 @@ class TradeDocument(Thing):
|
||||||
url = URL(description=m.TradeDocument.url.comment)
|
url = URL(description=m.TradeDocument.url.comment)
|
||||||
lot = NestedOn('Lot', only_query='id', description=m.TradeDocument.lot.__doc__)
|
lot = NestedOn('Lot', only_query='id', description=m.TradeDocument.lot.__doc__)
|
||||||
trading = SanitizedStr(dump_only=True, description='')
|
trading = SanitizedStr(dump_only=True, description='')
|
||||||
|
weight = Float(required=False, description=m.TradeDocument.weight.comment)
|
||||||
|
total_weight = Float(required=False, description=m.TradeDocument.weight.comment)
|
||||||
|
|
|
@ -28,6 +28,7 @@ from ereuse_devicehub.resources.action import models
|
||||||
from ereuse_devicehub.resources.device import states
|
from ereuse_devicehub.resources.device import states
|
||||||
from ereuse_devicehub.resources.device.models import Desktop, Device, GraphicCard, HardDrive, \
|
from ereuse_devicehub.resources.device.models import Desktop, Device, GraphicCard, HardDrive, \
|
||||||
RamModule, SolidStateDrive
|
RamModule, SolidStateDrive
|
||||||
|
from ereuse_devicehub.resources.tradedocument.models import TradeDocument
|
||||||
from ereuse_devicehub.resources.enums import ComputerChassis, Severity, TestDataStorageLength
|
from ereuse_devicehub.resources.enums import ComputerChassis, Severity, TestDataStorageLength
|
||||||
from tests import conftest
|
from tests import conftest
|
||||||
from tests.conftest import create_user, file, yaml2json, json_encode
|
from tests.conftest import create_user, file, yaml2json, json_encode
|
||||||
|
@ -2438,3 +2439,64 @@ def test_action_web_erase(user: UserClient, client: Client):
|
||||||
assert "alert alert-info" in response
|
assert "alert alert-info" in response
|
||||||
assert "100% coincidence." in response
|
assert "100% coincidence." in response
|
||||||
assert not "alert alert-danger" in response
|
assert not "alert alert-danger" in response
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_moveOnDocument(user: UserClient, user2: UserClient):
|
||||||
|
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': 15,
|
||||||
|
'container_from': tradedocument_from['id'],
|
||||||
|
'container_to': id_hash,
|
||||||
|
'description': description
|
||||||
|
}
|
||||||
|
doc, _ = user.post(res=models.Action, data=request_moveOn)
|
||||||
|
|
||||||
|
assert doc['weight'] == request_moveOn['weight']
|
||||||
|
assert doc['container_from']['id'] == tradedocument_from['id']
|
||||||
|
assert doc['container_to']['id'] == tradedocument_to['id']
|
||||||
|
|
||||||
|
mvs= models.MoveOnDocument.query.filter().first()
|
||||||
|
trade_from = TradeDocument.query.filter_by(id=tradedocument_from['id']).one()
|
||||||
|
trade_to = TradeDocument.query.filter_by(id=tradedocument_to['id']).one()
|
||||||
|
assert trade_from in mvs.documents
|
||||||
|
assert trade_to in mvs.documents
|
||||||
|
assert description == mvs.description
|
||||||
|
tradedocument_to, _ = user.post(res=TradeDocument, data=request_post2)
|
||||||
|
user.post(res=models.Action, data=request_moveOn, status=422)
|
||||||
|
|
|
@ -122,4 +122,4 @@ def test_api_docs(client: Client):
|
||||||
'scheme': 'basic',
|
'scheme': 'basic',
|
||||||
'name': 'Authorization'
|
'name': 'Authorization'
|
||||||
}
|
}
|
||||||
assert len(docs['definitions']) == 126
|
assert len(docs['definitions']) == 127
|
||||||
|
|
|
@ -16,6 +16,7 @@ from ereuse_devicehub.devicehub import Devicehub
|
||||||
from ereuse_devicehub.resources.user.models import Session
|
from ereuse_devicehub.resources.user.models import Session
|
||||||
from ereuse_devicehub.resources.action.models import Snapshot, Allocate, Live
|
from ereuse_devicehub.resources.action.models import Snapshot, Allocate, Live
|
||||||
from ereuse_devicehub.resources.documents import documents
|
from ereuse_devicehub.resources.documents import documents
|
||||||
|
from ereuse_devicehub.resources.tradedocument.models import TradeDocument
|
||||||
from ereuse_devicehub.resources.device import models as d
|
from ereuse_devicehub.resources.device import models as d
|
||||||
from ereuse_devicehub.resources.lot.models import Lot
|
from ereuse_devicehub.resources.lot.models import Lot
|
||||||
from ereuse_devicehub.resources.tag.model import Tag
|
from ereuse_devicehub.resources.tag.model import Tag
|
||||||
|
@ -677,3 +678,41 @@ def test_get_wbconf(user: UserClient):
|
||||||
assert token in env
|
assert token in env
|
||||||
user.user['token'] = token
|
user.user['token'] = token
|
||||||
snapshot, _ = user.post(file('basic.snapshot'), res=Snapshot)
|
snapshot, _ = user.post(file('basic.snapshot'), res=Snapshot)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_trade_documents(user: UserClient):
|
||||||
|
"""Tests upload one document"""
|
||||||
|
|
||||||
|
lot, _ = user.post({'name': 'MyLot'}, res=Lot)
|
||||||
|
request_post = {
|
||||||
|
'filename': 'test.pdf',
|
||||||
|
'hash': 'bbbbbbbb',
|
||||||
|
'url': 'http://www.ereuse.org/',
|
||||||
|
'lot': lot['id']
|
||||||
|
}
|
||||||
|
doc, _ = user.post(res=TradeDocument, data=request_post)
|
||||||
|
assert doc['filename'] == request_post['filename']
|
||||||
|
assert doc['url'] == request_post['url']
|
||||||
|
assert doc['hash'] == request_post['hash']
|
||||||
|
assert doc['lot']['name'] == lot['name']
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.mvp
|
||||||
|
@pytest.mark.usefixtures(conftest.app_context.__name__)
|
||||||
|
def test_trade_documents_with_weight(user: UserClient):
|
||||||
|
"""Tests upload one document"""
|
||||||
|
|
||||||
|
lot, _ = user.post({'name': 'MyLot'}, res=Lot)
|
||||||
|
# long url
|
||||||
|
url = 'http://www.ereuse.org/apapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaapaapapaapaapaapaapaapaapaapaapaapaaaa',
|
||||||
|
request_post = {
|
||||||
|
'filename': 'test.pdf',
|
||||||
|
'hash': 'bbbbbbbb',
|
||||||
|
'url': url,
|
||||||
|
'weight': 15,
|
||||||
|
'lot': lot['id']
|
||||||
|
}
|
||||||
|
doc, _ = user.post(res=TradeDocument, data=request_post)
|
||||||
|
assert doc['weight'] == request_post['weight']
|
||||||
|
|
Reference in New Issue