base for documents
This commit is contained in:
parent
5222f2ceb7
commit
c21de78982
|
@ -296,9 +296,10 @@ class ActionDevice(db.Model):
|
|||
primary_key=True)
|
||||
|
||||
|
||||
class ActionWithMultipleDocuments(Action):
|
||||
class ActionWithMultipleDocuments(ActionWithMultipleDevices):
|
||||
# pass
|
||||
documents = relationship(Document,
|
||||
backref=backref('actions_multiple', lazy=True, **_sorted_actions),
|
||||
backref=backref('actions_multiple_docs', lazy=True, **_sorted_actions),
|
||||
secondary=lambda: ActionDocument.__table__,
|
||||
order_by=lambda: Document.id,
|
||||
collection_class=OrderedSet)
|
||||
|
@ -1448,7 +1449,7 @@ class CancelReservation(Organize):
|
|||
"""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
|
||||
and the devices that confirm
|
||||
"""
|
||||
|
@ -1488,7 +1489,7 @@ class ConfirmRevoke(Confirm):
|
|||
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.
|
||||
Every time a trade action is performed, the old user looses its
|
||||
political possession, for example ownership, in favor of another
|
||||
|
|
|
@ -499,7 +499,6 @@ class ConfirmRevoke(ActionWithMultipleDevices):
|
|||
|
||||
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_id = SanitizedStr(validate=Length(max=STR_SIZE), data_key='userTo', missing='',
|
||||
|
|
10
ereuse_devicehub/resources/tradedocument/__init__.py
Normal file
10
ereuse_devicehub/resources/tradedocument/__init__.py
Normal file
|
@ -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
|
115
ereuse_devicehub/resources/tradedocument/models.py
Normal file
115
ereuse_devicehub/resources/tradedocument/models.py
Normal file
|
@ -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)
|
14
ereuse_devicehub/resources/tradedocument/schemas.py
Normal file
14
ereuse_devicehub/resources/tradedocument/schemas.py
Normal file
|
@ -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)
|
35
ereuse_devicehub/resources/tradedocument/views.py
Normal file
35
ereuse_devicehub/resources/tradedocument/views.py
Normal file
|
@ -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,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirm': False,
|
||||
'code': 'MAX'
|
||||
|
@ -84,7 +83,6 @@ def test_offer_without_to(user: UserClient):
|
|||
'userFrom': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirm': False,
|
||||
'code': 'MAX'
|
||||
|
@ -107,7 +105,6 @@ def test_offer_without_to(user: UserClient):
|
|||
'userFrom': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot2.id,
|
||||
'confirm': False,
|
||||
'code': 'MAX'
|
||||
|
@ -138,7 +135,6 @@ def test_offer_without_from(user: UserClient, user2: UserClient):
|
|||
'userTo': user2.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot.id,
|
||||
'confirm': False,
|
||||
'code': 'MAX'
|
||||
|
@ -183,7 +179,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,
|
||||
'confirm': False,
|
||||
'code': 'MAX'
|
||||
|
@ -217,7 +212,6 @@ def test_offer(user: UserClient):
|
|||
'userTo': user2.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot.id,
|
||||
'confirm': True,
|
||||
}
|
||||
|
@ -244,7 +238,6 @@ def test_offer_without_devices(user: UserClient):
|
|||
'userTo': user2.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirm': True,
|
||||
}
|
||||
|
@ -272,7 +265,6 @@ def test_endpoint_confirm(user: UserClient, user2: UserClient):
|
|||
'userTo': user2.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirm': True,
|
||||
}
|
||||
|
@ -313,7 +305,6 @@ def test_confirm_revoke(user: UserClient, user2: UserClient):
|
|||
'userTo': user2.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirm': True,
|
||||
}
|
||||
|
@ -392,7 +383,6 @@ def test_usecase_confirmation(user: UserClient, user2: UserClient):
|
|||
'userTo': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirm': True,
|
||||
}
|
||||
|
@ -580,7 +570,6 @@ def test_confirmRevoke(user: UserClient, user2: UserClient):
|
|||
'userTo': user.email,
|
||||
'price': 10,
|
||||
'date': "2020-12-01T02:00:00+00:00",
|
||||
'documentID': '1',
|
||||
'lot': lot['id'],
|
||||
'confirm': True,
|
||||
}
|
||||
|
|
Reference in a new issue