base for documents
This commit is contained in:
parent
5222f2ceb7
commit
c21de78982
|
@ -296,9 +296,10 @@ class ActionDevice(db.Model):
|
||||||
primary_key=True)
|
primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
class ActionWithMultipleDocuments(Action):
|
class ActionWithMultipleDocuments(ActionWithMultipleDevices):
|
||||||
|
# pass
|
||||||
documents = relationship(Document,
|
documents = relationship(Document,
|
||||||
backref=backref('actions_multiple', lazy=True, **_sorted_actions),
|
backref=backref('actions_multiple_docs', lazy=True, **_sorted_actions),
|
||||||
secondary=lambda: ActionDocument.__table__,
|
secondary=lambda: ActionDocument.__table__,
|
||||||
order_by=lambda: Document.id,
|
order_by=lambda: Document.id,
|
||||||
collection_class=OrderedSet)
|
collection_class=OrderedSet)
|
||||||
|
@ -1448,7 +1449,7 @@ class CancelReservation(Organize):
|
||||||
"""The act of cancelling a reservation."""
|
"""The act of cancelling a reservation."""
|
||||||
|
|
||||||
|
|
||||||
class Confirm(JoinedTableMixin, ActionWithMultipleDevices):
|
class Confirm(JoinedTableMixin, ActionWithMultipleDocuments):
|
||||||
"""Users confirm the one action trade this confirmation it's link to trade
|
"""Users confirm the one action trade this confirmation it's link to trade
|
||||||
and the devices that confirm
|
and the devices that confirm
|
||||||
"""
|
"""
|
||||||
|
@ -1488,7 +1489,7 @@ class ConfirmRevoke(Confirm):
|
||||||
return '<{0.t} {0.id} accepted by {0.user}>'.format(self)
|
return '<{0.t} {0.id} accepted by {0.user}>'.format(self)
|
||||||
|
|
||||||
|
|
||||||
class Trade(JoinedTableMixin, ActionWithMultipleDevices, ActionWithMultipleDocuments):
|
class Trade(JoinedTableMixin, ActionWithMultipleDocuments):
|
||||||
"""Trade actions log the political exchange of devices between users.
|
"""Trade actions log the political exchange of devices between users.
|
||||||
Every time a trade action is performed, the old user looses its
|
Every time a trade action is performed, the old user looses its
|
||||||
political possession, for example ownership, in favor of another
|
political possession, for example ownership, in favor of another
|
||||||
|
|
|
@ -499,7 +499,6 @@ class ConfirmRevoke(ActionWithMultipleDevices):
|
||||||
|
|
||||||
class Trade(ActionWithMultipleDevices):
|
class Trade(ActionWithMultipleDevices):
|
||||||
__doc__ = m.Trade.__doc__
|
__doc__ = m.Trade.__doc__
|
||||||
document_id = SanitizedStr(validate=Length(max=STR_SIZE), data_key='documentID', required=False)
|
|
||||||
date = DateTime(data_key='date', required=False)
|
date = DateTime(data_key='date', required=False)
|
||||||
price = Float(required=False, data_key='price')
|
price = Float(required=False, data_key='price')
|
||||||
user_to_id = SanitizedStr(validate=Length(max=STR_SIZE), data_key='userTo', missing='',
|
user_to_id = SanitizedStr(validate=Length(max=STR_SIZE), data_key='userTo', missing='',
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
from teal.resource import Converters, Resource
|
||||||
|
|
||||||
|
from ereuse_devicehub.resources.tradedocument import schemas
|
||||||
|
from ereuse_devicehub.resources.tradedocument.views import DocumentView
|
||||||
|
|
||||||
|
class TradeDocumentDef(Resource):
|
||||||
|
SCHEMA = schemas.Document
|
||||||
|
VIEW = DocumentView
|
||||||
|
AUTH = True
|
||||||
|
ID_CONVERTER = Converters.string
|
|
@ -0,0 +1,115 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from itertools import chain
|
||||||
|
from citext import CIText
|
||||||
|
from flask import current_app as app, g
|
||||||
|
|
||||||
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
|
from ereuse_devicehub.db import db
|
||||||
|
from ereuse_devicehub.resources.user.models import User
|
||||||
|
from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing, listener_reset_field_updated_in_actual_time
|
||||||
|
|
||||||
|
from sqlalchemy import BigInteger, Boolean, Column, Float, ForeignKey, Integer, \
|
||||||
|
Sequence, SmallInteger, Unicode, inspect, text
|
||||||
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
|
from sqlalchemy.orm import ColumnProperty, backref, relationship, validates
|
||||||
|
from sqlalchemy.util import OrderedSet
|
||||||
|
from sqlalchemy_utils import ColorType
|
||||||
|
from teal.db import CASCADE_DEL, POLYMORPHIC_ID, POLYMORPHIC_ON, \
|
||||||
|
check_lower, check_range
|
||||||
|
from teal.resource import url_for_resource
|
||||||
|
|
||||||
|
from ereuse_devicehub.resources.utils import hashcode
|
||||||
|
from ereuse_devicehub.resources.enums import BatteryTechnology, CameraFacing, ComputerChassis, \
|
||||||
|
DataStorageInterface, DisplayTech, PrinterTechnology, RamFormat, RamInterface, Severity, TransferState
|
||||||
|
|
||||||
|
|
||||||
|
class Document(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)
|
||||||
|
file_name = Column(db.CIText())
|
||||||
|
file_name.comment = """This is the name of the file when user up the document."""
|
||||||
|
file_name_disk = Column(db.CIText())
|
||||||
|
file_name_disk.comment = """This is the name of the file as devicehub save in server."""
|
||||||
|
|
||||||
|
__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_multiple_docs, key=lambda x: x.created)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path_to_file(self) -> str:
|
||||||
|
"""The path of one file is defined by the owner, file_name and created time.
|
||||||
|
|
||||||
|
"""
|
||||||
|
base = app.config['PATH_DOCUMENTS_STORAGE']
|
||||||
|
file_name = "{0.date}-{0.filename}".format(self)
|
||||||
|
base = os.path.join(base, g.user.email, file_name)
|
||||||
|
return sorted(self.actions_multiple_docs, key=lambda x: x.created)
|
||||||
|
|
||||||
|
def last_action_of(self, *types):
|
||||||
|
"""Gets the last action of the given types.
|
||||||
|
|
||||||
|
:raise LookupError: Device has not an action of the given type.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# noinspection PyTypeHints
|
||||||
|
actions = self.actions
|
||||||
|
actions.sort(key=lambda x: x.created)
|
||||||
|
return next(e for e in reversed(actions) if isinstance(e, types))
|
||||||
|
except StopIteration:
|
||||||
|
raise LookupError('{!r} does not contain actions of types {}.'.format(self, types))
|
||||||
|
|
||||||
|
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,14 @@
|
||||||
|
from marshmallow.fields import DateTime, Integer
|
||||||
|
from teal.marshmallow import SanitizedStr
|
||||||
|
|
||||||
|
from ereuse_devicehub.resources.schemas import Thing
|
||||||
|
from ereuse_devicehub.resources.tradedocument import models as m
|
||||||
|
|
||||||
|
|
||||||
|
class Document(Thing):
|
||||||
|
__doc__ = m.Document.__doc__
|
||||||
|
id = Integer(description=m.Document.id.comment, dump_only=True)
|
||||||
|
date = DateTime(required=False, description=m.Document.date.comment)
|
||||||
|
id_document = SanitizedStr(default='', description=m.Document.id_document.comment)
|
||||||
|
description = SanitizedStr(default='', description=m.Document.description.comment)
|
||||||
|
file_name = SanitizedStr(default='', description=m.Document.file_name.comment)
|
|
@ -0,0 +1,35 @@
|
||||||
|
|
||||||
|
import marshmallow
|
||||||
|
from flask import g, current_app as app, render_template, request, Response
|
||||||
|
from flask.json import jsonify
|
||||||
|
from flask_sqlalchemy import Pagination
|
||||||
|
from marshmallow import fields, fields as f, validate as v, Schema as MarshmallowSchema
|
||||||
|
from teal.resource import View
|
||||||
|
|
||||||
|
from ereuse_devicehub import auth
|
||||||
|
from ereuse_devicehub.db import db
|
||||||
|
from ereuse_devicehub.query import SearchQueryParser, things_response
|
||||||
|
from ereuse_devicehub.resources.tradedocument.models import Document
|
||||||
|
|
||||||
|
class DocumentView(View):
|
||||||
|
|
||||||
|
# @auth.Auth.requires_auth
|
||||||
|
def one(self, id: str):
|
||||||
|
document = Document.query.filter_by(id=id).first()
|
||||||
|
return self.schema.jsonify(document)
|
||||||
|
|
||||||
|
# @auth.Auth.requires_auth
|
||||||
|
def post(self):
|
||||||
|
"""Posts an action."""
|
||||||
|
json = request.get_json(validate=False)
|
||||||
|
resource_def = app.resources[json['type']]
|
||||||
|
|
||||||
|
a = resource_def.schema.load(json)
|
||||||
|
Model = db.Model._decl_class_registry.data[json['type']]()
|
||||||
|
action = Model(**a)
|
||||||
|
db.session.add(action)
|
||||||
|
db.session().final_flush()
|
||||||
|
ret = self.schema.jsonify(action)
|
||||||
|
ret.status_code = 201
|
||||||
|
db.session.commit()
|
||||||
|
return ret
|
|
@ -56,7 +56,6 @@ def test_offer_without_to(user: UserClient):
|
||||||
'userFrom': user.email,
|
'userFrom': user.email,
|
||||||
'price': 10,
|
'price': 10,
|
||||||
'date': "2020-12-01T02:00:00+00:00",
|
'date': "2020-12-01T02:00:00+00:00",
|
||||||
'documentID': '1',
|
|
||||||
'lot': lot['id'],
|
'lot': lot['id'],
|
||||||
'confirm': False,
|
'confirm': False,
|
||||||
'code': 'MAX'
|
'code': 'MAX'
|
||||||
|
@ -84,7 +83,6 @@ def test_offer_without_to(user: UserClient):
|
||||||
'userFrom': user.email,
|
'userFrom': user.email,
|
||||||
'price': 10,
|
'price': 10,
|
||||||
'date': "2020-12-01T02:00:00+00:00",
|
'date': "2020-12-01T02:00:00+00:00",
|
||||||
'documentID': '1',
|
|
||||||
'lot': lot['id'],
|
'lot': lot['id'],
|
||||||
'confirm': False,
|
'confirm': False,
|
||||||
'code': 'MAX'
|
'code': 'MAX'
|
||||||
|
@ -107,7 +105,6 @@ def test_offer_without_to(user: UserClient):
|
||||||
'userFrom': user.email,
|
'userFrom': user.email,
|
||||||
'price': 10,
|
'price': 10,
|
||||||
'date': "2020-12-01T02:00:00+00:00",
|
'date': "2020-12-01T02:00:00+00:00",
|
||||||
'documentID': '1',
|
|
||||||
'lot': lot2.id,
|
'lot': lot2.id,
|
||||||
'confirm': False,
|
'confirm': False,
|
||||||
'code': 'MAX'
|
'code': 'MAX'
|
||||||
|
@ -138,7 +135,6 @@ def test_offer_without_from(user: UserClient, user2: UserClient):
|
||||||
'userTo': user2.email,
|
'userTo': user2.email,
|
||||||
'price': 10,
|
'price': 10,
|
||||||
'date': "2020-12-01T02:00:00+00:00",
|
'date': "2020-12-01T02:00:00+00:00",
|
||||||
'documentID': '1',
|
|
||||||
'lot': lot.id,
|
'lot': lot.id,
|
||||||
'confirm': False,
|
'confirm': False,
|
||||||
'code': 'MAX'
|
'code': 'MAX'
|
||||||
|
@ -183,7 +179,6 @@ def test_offer_without_users(user: UserClient):
|
||||||
'devices': [device.id],
|
'devices': [device.id],
|
||||||
'price': 10,
|
'price': 10,
|
||||||
'date': "2020-12-01T02:00:00+00:00",
|
'date': "2020-12-01T02:00:00+00:00",
|
||||||
'documentID': '1',
|
|
||||||
'lot': lot.id,
|
'lot': lot.id,
|
||||||
'confirm': False,
|
'confirm': False,
|
||||||
'code': 'MAX'
|
'code': 'MAX'
|
||||||
|
@ -217,7 +212,6 @@ def test_offer(user: UserClient):
|
||||||
'userTo': user2.email,
|
'userTo': user2.email,
|
||||||
'price': 10,
|
'price': 10,
|
||||||
'date': "2020-12-01T02:00:00+00:00",
|
'date': "2020-12-01T02:00:00+00:00",
|
||||||
'documentID': '1',
|
|
||||||
'lot': lot.id,
|
'lot': lot.id,
|
||||||
'confirm': True,
|
'confirm': True,
|
||||||
}
|
}
|
||||||
|
@ -244,7 +238,6 @@ def test_offer_without_devices(user: UserClient):
|
||||||
'userTo': user2.email,
|
'userTo': user2.email,
|
||||||
'price': 10,
|
'price': 10,
|
||||||
'date': "2020-12-01T02:00:00+00:00",
|
'date': "2020-12-01T02:00:00+00:00",
|
||||||
'documentID': '1',
|
|
||||||
'lot': lot['id'],
|
'lot': lot['id'],
|
||||||
'confirm': True,
|
'confirm': True,
|
||||||
}
|
}
|
||||||
|
@ -272,7 +265,6 @@ def test_endpoint_confirm(user: UserClient, user2: UserClient):
|
||||||
'userTo': user2.email,
|
'userTo': user2.email,
|
||||||
'price': 10,
|
'price': 10,
|
||||||
'date': "2020-12-01T02:00:00+00:00",
|
'date': "2020-12-01T02:00:00+00:00",
|
||||||
'documentID': '1',
|
|
||||||
'lot': lot['id'],
|
'lot': lot['id'],
|
||||||
'confirm': True,
|
'confirm': True,
|
||||||
}
|
}
|
||||||
|
@ -313,7 +305,6 @@ def test_confirm_revoke(user: UserClient, user2: UserClient):
|
||||||
'userTo': user2.email,
|
'userTo': user2.email,
|
||||||
'price': 10,
|
'price': 10,
|
||||||
'date': "2020-12-01T02:00:00+00:00",
|
'date': "2020-12-01T02:00:00+00:00",
|
||||||
'documentID': '1',
|
|
||||||
'lot': lot['id'],
|
'lot': lot['id'],
|
||||||
'confirm': True,
|
'confirm': True,
|
||||||
}
|
}
|
||||||
|
@ -392,7 +383,6 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient):
|
||||||
'userTo': user.email,
|
'userTo': user.email,
|
||||||
'price': 10,
|
'price': 10,
|
||||||
'date': "2020-12-01T02:00:00+00:00",
|
'date': "2020-12-01T02:00:00+00:00",
|
||||||
'documentID': '1',
|
|
||||||
'lot': lot['id'],
|
'lot': lot['id'],
|
||||||
'confirm': True,
|
'confirm': True,
|
||||||
}
|
}
|
||||||
|
@ -580,7 +570,6 @@ def test_confirmRevoke(user: UserClient, user2: UserClient):
|
||||||
'userTo': user.email,
|
'userTo': user.email,
|
||||||
'price': 10,
|
'price': 10,
|
||||||
'date': "2020-12-01T02:00:00+00:00",
|
'date': "2020-12-01T02:00:00+00:00",
|
||||||
'documentID': '1',
|
|
||||||
'lot': lot['id'],
|
'lot': lot['id'],
|
||||||
'confirm': True,
|
'confirm': True,
|
||||||
}
|
}
|
||||||
|
|
Reference in New Issue