Merge remote-tracking branch 'origin/mvp_deliverynote' into mvp

This commit is contained in:
nad 2020-03-03 12:52:28 +01:00
commit 4c8a876f97
7 changed files with 375 additions and 1 deletions

4
.gitignore vendored
View File

@ -3,6 +3,10 @@ __pycache__/
*.py[cod]
*$py.class
# Vim swap files
*.swp
*.swo
# C extensions
*.so

View File

@ -7,7 +7,7 @@ from teal.config import Config
from teal.enums import Currency
from teal.utils import import_resource
from ereuse_devicehub.resources import action, agent, inventory, lot, tag, user
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.enums import PriceSoftware
@ -20,6 +20,7 @@ class DevicehubConfig(Config):
import_resource(tag),
import_resource(agent),
import_resource(lot),
import_resource(deliverynote),
import_resource(documents),
import_resource(inventory)),
)

View File

@ -0,0 +1,29 @@
import pathlib
from typing import Callable, Iterable, Tuple
from teal.resource import Converters, Resource
from ereuse_devicehub.db import db
from ereuse_devicehub.resources.deliverynote import schemas
from ereuse_devicehub.resources.deliverynote.views import DeliverynoteView
class DeliverynoteDef(Resource):
SCHEMA = schemas.Deliverynote
VIEW = DeliverynoteView
AUTH = True
# AUTH = False
ID_CONVERTER = Converters.uuid
def __init__(self, app,
import_name=__name__.split('.')[0],
static_folder=None,
static_url_path=None,
template_folder=None,
url_prefix=None,
subdomain=None,
url_defaults=None,
root_path=None,
cli_commands: Iterable[Tuple[Callable, str or None]] = tuple()):
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
url_prefix, subdomain, url_defaults, root_path, cli_commands)

View File

@ -0,0 +1,120 @@
import uuid
from datetime import datetime
from typing import Union
from boltons import urlutils
from citext import CIText
from flask import g
from sqlalchemy import TEXT, Enum as DBEnum
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy_utils import LtreeType
from sqlalchemy_utils.types.ltree import LQUERY
from teal.db import CASCADE_OWN, UUIDLtree, check_range, IntEnum
from teal.resource import url_for_resource
from ereuse_devicehub.db import create_view, db, exp, f
from ereuse_devicehub.resources.models import Thing
from ereuse_devicehub.resources.user.models import User
from ereuse_devicehub.resources.lot.models import Lot
from ereuse_devicehub.resources.enums import TransferState
class Deliverynote(Thing):
id = db.Column(UUID(as_uuid=True), primary_key=True) # uuid is generated on init by default
document_id = db.Column(CIText(), nullable=False)
creator_id = db.Column(UUID(as_uuid=True),
db.ForeignKey(User.id),
nullable=False,
default=lambda: g.user.id)
creator = db.relationship(User, primaryjoin=creator_id == User.id)
supplier_email = db.Column(CIText(),
db.ForeignKey(User.email),
nullable=False,
default=lambda: g.user.email)
supplier = db.relationship(User, primaryjoin=lambda: Deliverynote.supplier_email == User.email)
# supplier = db.relationship(User)
date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
# deposit = db.Column(db.Integer, check_range('deposit', min=0, max=100), default=0)
deposit = db.Column(CIText(), nullable=False)
# The following fiels are supposed to be 0:N relationships
# to SnapshotDelivery entity.
# At this stage of implementation they will treated as a
# comma-separated string of the devices expexted/transfered
expected_devices = db.Column(CIText(), nullable=False)
transferred_devices = db.Column(CIText(), nullable=False)
transfer_state = db.Column(IntEnum(TransferState), default=TransferState.Initial, nullable=False)
transfer_state.comment = TransferState.__doc__
lot_id = db.Column(UUID(as_uuid=True),
db.ForeignKey(Lot.id),
nullable=False)
lots = db.relationship(Lot,
backref=db.backref('deliverynotes', lazy=True, collection_class=set),
lazy=True,
primaryjoin=Lot.id == lot_id,
collection_class=set)
def __init__(self, document_id: str, deposit: str,
supplier_email: str,
expected_devices: str,
transferred_devices: str) -> None:
"""Initializes a delivery note
"""
super().__init__(id=uuid.uuid4(),
document_id=document_id, deposit=deposit,
supplier_email=supplier_email,
expected_devices=expected_devices,
transferred_devices=transferred_devices)
@property
def type(self) -> str:
return self.__class__.__name__
@property
def url(self) -> urlutils.URL:
"""The URL where to GET this action."""
return urlutils.URL(url_for_resource(Deliverynote, item_id=self.id))
# def delete(self):
# """Deletes the lot.
# This method removes the children lots and children
# devices orphan from this lot and then marks this lot
# for deletion.
# """
# self.remove_children(*self.children)
# db.session.delete(self)
# def _refresh_models_with_relationships_to_lots(self):
# session = db.Session.object_session(self)
# for model in session:
# if isinstance(model, (Device, Lot, Path)):
# session.expire(model)
# def __contains__(self, child: Union['Lot', Device]):
# if isinstance(child, Lot):
# return Path.has_lot(self.id, child.id)
# elif isinstance(child, Device):
# device = db.session.query(LotDeviceDescendants) \
# .filter(LotDeviceDescendants.device_id == child.id) \
# .filter(LotDeviceDescendants.ancestor_lot_id == self.id) \
# .one_or_none()
# return device
# else:
# raise TypeError('Lot only contains devices and lots, not {}'.format(child.__class__))
def __repr__(self) -> str:
# return '<Lot {0.name} devices={0.devices!r}>'.format(self)
return '<Deliverynote {0.documentID}>'.format(self)
# class LotDevice(db.Model):
# device_id = db.Column(db.BigInteger, db.ForeignKey(Device.id), primary_key=True)
# lot_id = db.Column(UUID(as_uuid=True), db.ForeignKey(Lot.id), primary_key=True)
# created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
# author_id = db.Column(UUID(as_uuid=True),
# db.ForeignKey(User.id),
# nullable=False,
# default=lambda: g.user.id)
# author = db.relationship(User, primaryjoin=author_id == User.id)
# author_id.comment = """The user that put the device in the lot."""

View File

@ -0,0 +1,103 @@
import uuid
from datetime import datetime
from typing import Iterable, Optional, Set, Union
from uuid import UUID
from boltons import urlutils
from sqlalchemy import Column
from sqlalchemy.orm import Query, relationship
from sqlalchemy_utils import Ltree
from ereuse_devicehub.db import db
from ereuse_devicehub.resources.device.models import Device
from ereuse_devicehub.resources.models import Thing
LotQuery = Union[Query, Iterable['Lot']]
class Lot(Thing):
id = ... # type: Column
name = ... # type: Column
closed = ... # type: Column
devices = ... # type: relationship
paths = ... # type: relationship
description = ... # type: Column
all_devices = ... # type: relationship
parents = ... # type: relationship
deposit = ... # type: Column
owner_address = ... # type: Column
owner = ... # type: relationship
transfer_state = ... # type: Column
receiver_address = ... # type: Column
receiver = ... # type: relationship
deliverynote_address = ... # type: Column
def __init__(self, name: str, closed: bool = closed.default.arg) -> None:
super().__init__()
self.id = ... # type: UUID
self.name = ... # type: str
self.closed = ... # type: bool
self.devices = ... # type: Set[Device]
self.paths = ... # type: Set[Path]
self.description = ... # type: str
self.all_devices = ... # type: Set[Device]
self.parents = ... # type: Set[Lot]
self.children = ... # type: Set[Lot]
self.owner_address = ... # type: UUID
self.transfer_state = ...
self.receiver_address = ... # type: str
self.deliverynote_address = ... # type: str
def add_children(self, *children: Union[Lot, uuid.UUID]):
pass
def remove_children(self, *children: Union[Lot, uuid.UUID]):
pass
@classmethod
def roots(cls) -> LotQuery:
pass
@property
def descendants(self) -> LotQuery:
pass
@classmethod
def descendantsq(cls, id) -> LotQuery:
pass
@property
def url(self) -> urlutils.URL:
pass
def delete(self):
pass
class Path:
id = ... # type: Column
lot_id = ... # type: Column
lot = ... # type: relationship
path = ... # type: Column
created = ... # type: Column
def __init__(self, lot: Lot) -> None:
super().__init__()
self.id = ... # type: UUID
self.lot = ... # type: Lot
self.path = ... # type: Ltree
self.created = ... # type: datetime
class LotDeviceDescendants(db.Model):
device_id = ... # type: Column
ancestor_lot_id = ... # type: Column
parent_lot_id = ... # type: Column
device_parent_id = ... # type: Column
def __init__(self) -> None:
super().__init__()
self.device_id = ... # type: int
self.ancestor_lot_id = ... # type: UUID
self.parent_lot_id = ... # type: UUID
self.device_parent_id = ... # type: Optional[int]

View File

@ -0,0 +1,24 @@
from marshmallow import fields as f
from teal.marshmallow import SanitizedStr, URL, EnumField
from ereuse_devicehub.marshmallow import NestedOn
from ereuse_devicehub.resources.deliverynote import models as m
from ereuse_devicehub.resources.user import schemas as s_user
from ereuse_devicehub.resources.models import STR_SIZE
from ereuse_devicehub.resources.schemas import Thing
from ereuse_devicehub.resources.enums import TransferState
class Deliverynote(Thing):
id = f.UUID(dump_only=True)
document_id = SanitizedStr(validate=f.validate.Length(max=STR_SIZE), required=True)
url = URL(dump_only=True, description=m.Deliverynote.url.__doc__)
creator = NestedOn(s_user.User,only_query='id')
supplier_email = SanitizedStr(validate=f.validate.Length(max=STR_SIZE), required=True)
supplier = NestedOn(s_user.User,only_query='id')
# deposit = f.Integer(validate=f.validate.Range(min=0, max=100),
# description=m.Lot.deposit.__doc__)
deposit = SanitizedStr(validate=f.validate.Length(max=STR_SIZE), required=True)
expected_devices = SanitizedStr(validate=f.validate.Length(max=STR_SIZE), required=True)
transferred_devices = SanitizedStr(validate=f.validate.Length(max=STR_SIZE), required=True)
transfer_state = EnumField(TransferState, description=m.Lot.transfer_state.comment)

View File

@ -0,0 +1,93 @@
import datetime
import uuid
from collections import deque
from enum import Enum
from typing import Dict, List, Set, Union
import marshmallow as ma
import teal.cache
from flask import Response, jsonify, request
from marshmallow import Schema as MarshmallowSchema, fields as f
from teal.marshmallow import EnumField
from teal.resource import View
from sqlalchemy.orm import joinedload
from ereuse_devicehub.db import db
from ereuse_devicehub.query import things_response
from ereuse_devicehub.resources.deliverynote.models import Deliverynote
from ereuse_devicehub.resources.lot.models import Lot
class DeliverynoteView(View):
class FindArgs(MarshmallowSchema):
"""Allowed arguments for the ``find``
method (GET collection) endpoint
"""
search = f.Str(missing=None)
def post(self):
# Create delivery note
dn = request.get_json()
dlvnote = Deliverynote(**dn)
# Create a lot
lot_name = dlvnote.supplier_email + "_" + datetime.datetime.utcnow().strftime("%B-%d-%Y")
new_lot = Lot(name=lot_name)
dlvnote.lot_id = new_lot.id
db.session.add(new_lot)
db.session.add(dlvnote)
db.session().final_flush()
ret = self.schema.jsonify(dlvnote)
ret.status_code = 201
db.session.commit()
return ret
# def patch(self, id):
# patch_schema = self.resource_def.SCHEMA(only=('name', 'description', 'transfer_state', 'receiver_address', 'deposit', 'deliverynote_address', 'devices', 'owner_address'), partial=True)
# d = request.get_json(schema=patch_schema)
# dlvnote = Deliverynote.query.filter_by(id=id).one()
# device_fields = ['transfer_state', 'receiver_address', 'deposit', 'deliverynote_address', 'owner_address']
# computers = [x for x in dlvnote.all_devices if isinstance(x, Computer)]
# for key, value in d.items():
# setattr(dlvnote, key, value)
# if key in device_fields:
# for dev in computers:
# setattr(dev, key, value)
# db.session.commit()
# return Response(status=204)
def one(self, id: uuid.UUID):
"""Gets one action."""
deliverynote = Deliverynote.query.filter_by(id=id).one() # type: Deliverynote
return self.schema.jsonify(deliverynote)
@teal.cache.cache(datetime.timedelta(minutes=5))
def find(self, args: dict):
"""Gets deliverynotes.
By passing the value `UiTree` in the parameter `format`
of the query you get a recursive nested suited for ui-tree::
[
{title: 'lot1',
nodes: [{title: 'child1', nodes:[]}]
]
Note that in this format filters are ignored.
Otherwise it just returns the standard flat view of lots that
you can filter.
"""
query = Deliverynote.query
if args['search']:
query = query.filter(Deliverynote.name.ilike(args['search'] + '%'))
dlvnote = query.paginate(per_page=6 if args['search'] else 30)
return things_response(
self.schema.dump(dlvnote.items, many=True, nested=0),
dlvnote.page, dlvnote.per_page, dlvnote.total, dlvnote.prev_num, dlvnote.next_num
)
def delete(self, id):
dlvnote = Deliverynote.query.filter_by(id=id).one()
dlvnote.delete()
db.session.commit()
return Response(status=204)