Update pagination and return URL for Lot, Device, Event resources
This commit is contained in:
parent
d1a23cbf9d
commit
4f0493c464
|
@ -5,6 +5,7 @@ from itertools import chain
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
from typing import Dict, List, Set
|
from typing import Dict, List, Set
|
||||||
|
|
||||||
|
from boltons import urlutils
|
||||||
from citext import CIText
|
from citext import CIText
|
||||||
from ereuse_utils.naming import Naming
|
from ereuse_utils.naming import Naming
|
||||||
from sqlalchemy import BigInteger, Boolean, Column, Enum as DBEnum, Float, ForeignKey, Integer, \
|
from sqlalchemy import BigInteger, Boolean, Column, Enum as DBEnum, Float, ForeignKey, Integer, \
|
||||||
|
@ -17,6 +18,7 @@ from stdnum import imei, meid
|
||||||
from teal.db import CASCADE, POLYMORPHIC_ID, POLYMORPHIC_ON, ResourceNotFound, URL, check_lower, \
|
from teal.db import CASCADE, POLYMORPHIC_ID, POLYMORPHIC_ON, ResourceNotFound, URL, check_lower, \
|
||||||
check_range
|
check_range
|
||||||
from teal.marshmallow import ValidationError
|
from teal.marshmallow import ValidationError
|
||||||
|
from teal.resource import url_for_resource
|
||||||
|
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.resources.enums import ComputerChassis, DataStorageInterface, DisplayTech, \
|
from ereuse_devicehub.resources.enums import ComputerChassis, DataStorageInterface, DisplayTech, \
|
||||||
|
@ -56,9 +58,7 @@ class Device(Thing):
|
||||||
"""
|
"""
|
||||||
depth = Column(Float(decimal_return_scale=3), check_range('depth', 0.1, 3))
|
depth = Column(Float(decimal_return_scale=3), check_range('depth', 0.1, 3))
|
||||||
color = Column(ColorType)
|
color = Column(ColorType)
|
||||||
color.comment = """
|
color.comment = """The predominant color of the device."""
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def events(self) -> list:
|
def events(self) -> list:
|
||||||
|
@ -93,6 +93,11 @@ class Device(Thing):
|
||||||
and not getattr(c, 'foreign_keys', None)
|
and not getattr(c, 'foreign_keys', None)
|
||||||
and c.key not in {'id', 'type', 'created', 'updated', 'parent_id', 'hid'}}
|
and c.key not in {'id', 'type', 'created', 'updated', 'parent_id', 'hid'}}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self) -> urlutils.URL:
|
||||||
|
"""The URL where to GET this device."""
|
||||||
|
return urlutils.URL(url_for_resource(Device, item_id=self.id))
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def __mapper_args__(cls):
|
def __mapper_args__(cls):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from typing import Dict, List, Set
|
from typing import Dict, List, Set
|
||||||
|
|
||||||
|
from boltons import urlutils
|
||||||
from boltons.urlutils import URL
|
from boltons.urlutils import URL
|
||||||
from colour import Color
|
from colour import Color
|
||||||
from sqlalchemy import Column, Integer
|
from sqlalchemy import Column, Integer
|
||||||
|
@ -51,6 +52,9 @@ class Device(Thing):
|
||||||
self.tags = ... # type: Set[Tag]
|
self.tags = ... # type: Set[Tag]
|
||||||
self.lots = ... # type: Set[Lot]
|
self.lots = ... # type: Set[Lot]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self) -> urlutils.URL:
|
||||||
|
pass
|
||||||
|
|
||||||
class DisplayMixin:
|
class DisplayMixin:
|
||||||
technology = ... # type: Column
|
technology = ... # type: Column
|
||||||
|
|
|
@ -29,6 +29,7 @@ class Device(Thing):
|
||||||
height = Float(validate=Range(0.1, 3), unit=UnitCodes.m, description=m.Device.height.comment)
|
height = Float(validate=Range(0.1, 3), unit=UnitCodes.m, description=m.Device.height.comment)
|
||||||
events = NestedOn('Event', many=True, dump_only=True, description=m.Device.events.__doc__)
|
events = NestedOn('Event', many=True, dump_only=True, description=m.Device.events.__doc__)
|
||||||
events_one = NestedOn('Event', many=True, load_only=True, collection_class=OrderedSet)
|
events_one = NestedOn('Event', many=True, load_only=True, collection_class=OrderedSet)
|
||||||
|
url = URL(dump_only=True, description=m.Device.url.__doc__)
|
||||||
|
|
||||||
@pre_load
|
@pre_load
|
||||||
def from_events_to_events_one(self, data: dict):
|
def from_events_to_events_one(self, data: dict):
|
||||||
|
|
|
@ -114,7 +114,10 @@ class DeviceView(View):
|
||||||
'page': devices.page,
|
'page': devices.page,
|
||||||
'perPage': devices.per_page,
|
'perPage': devices.per_page,
|
||||||
'total': devices.total,
|
'total': devices.total,
|
||||||
}
|
'previous': devices.prev_num,
|
||||||
|
'next': devices.next_num
|
||||||
|
},
|
||||||
|
'url': request.path
|
||||||
}
|
}
|
||||||
return jsonify(ret)
|
return jsonify(ret)
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ from datetime import datetime, timedelta
|
||||||
from typing import Set, Union
|
from typing import Set, Union
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from boltons import urlutils
|
||||||
from citext import CIText
|
from citext import CIText
|
||||||
from flask import current_app as app, g
|
from flask import current_app as app, g
|
||||||
from sqlalchemy import BigInteger, Boolean, CheckConstraint, Column, DateTime, Enum as DBEnum, \
|
from sqlalchemy import BigInteger, Boolean, CheckConstraint, Column, DateTime, Enum as DBEnum, \
|
||||||
|
@ -17,6 +18,7 @@ from teal.db import ArrayOfEnum, CASCADE, CASCADE_OWN, INHERIT_COND, IP, POLYMOR
|
||||||
POLYMORPHIC_ON, StrictVersionType, URL, check_lower, check_range
|
POLYMORPHIC_ON, StrictVersionType, URL, check_lower, check_range
|
||||||
from teal.enums import Country, Currency, Subdivision
|
from teal.enums import Country, Currency, Subdivision
|
||||||
from teal.marshmallow import ValidationError
|
from teal.marshmallow import ValidationError
|
||||||
|
from teal.resource import url_for_resource
|
||||||
|
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.resources.agent.models import Agent
|
from ereuse_devicehub.resources.agent.models import Agent
|
||||||
|
@ -163,6 +165,11 @@ class Event(Thing):
|
||||||
would point to the computer that contained this data storage, if any.
|
would point to the computer that contained this data storage, if any.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self) -> urlutils.URL:
|
||||||
|
"""The URL where to GET this event."""
|
||||||
|
return urlutils.URL(url_for_resource(Event, item_id=self.id))
|
||||||
|
|
||||||
# noinspection PyMethodParameters
|
# noinspection PyMethodParameters
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def __mapper_args__(cls):
|
def __mapper_args__(cls):
|
||||||
|
|
|
@ -5,6 +5,7 @@ from distutils.version import StrictVersion
|
||||||
from typing import Dict, List, Set, Union
|
from typing import Dict, List, Set, Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
from boltons import urlutils
|
||||||
from boltons.urlutils import URL
|
from boltons.urlutils import URL
|
||||||
from sqlalchemy import Column
|
from sqlalchemy import Column
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
@ -62,6 +63,9 @@ class Event(Thing):
|
||||||
self.agent = ... # type: Agent
|
self.agent = ... # type: Agent
|
||||||
self.author = ... # type: User
|
self.author = ... # type: User
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self) -> urlutils.URL:
|
||||||
|
pass
|
||||||
|
|
||||||
class EventWithOneDevice(Event):
|
class EventWithOneDevice(Event):
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,11 @@ import decimal
|
||||||
from flask import current_app as app
|
from flask import current_app as app
|
||||||
from marshmallow import Schema as MarshmallowSchema, ValidationError, validates_schema
|
from marshmallow import Schema as MarshmallowSchema, ValidationError, validates_schema
|
||||||
from marshmallow.fields import Boolean, DateTime, Decimal, Float, Integer, List, Nested, String, \
|
from marshmallow.fields import Boolean, DateTime, Decimal, Float, Integer, List, Nested, String, \
|
||||||
TimeDelta, URL, UUID
|
TimeDelta, UUID
|
||||||
from marshmallow.validate import Length, Range
|
from marshmallow.validate import Length, Range
|
||||||
from sqlalchemy.util import OrderedSet
|
from sqlalchemy.util import OrderedSet
|
||||||
from teal.enums import Country, Currency, Subdivision
|
from teal.enums import Country, Currency, Subdivision
|
||||||
from teal.marshmallow import EnumField, IP, SanitizedStr, Version
|
from teal.marshmallow import EnumField, IP, SanitizedStr, Version, URL
|
||||||
from teal.resource import Schema
|
from teal.resource import Schema
|
||||||
|
|
||||||
from ereuse_devicehub.marshmallow import NestedOn
|
from ereuse_devicehub.marshmallow import NestedOn
|
||||||
|
@ -38,6 +38,7 @@ class Event(Thing):
|
||||||
author = NestedOn(User, dump_only=True, exclude=('token',))
|
author = NestedOn(User, dump_only=True, exclude=('token',))
|
||||||
components = NestedOn(Component, dump_only=True, many=True)
|
components = NestedOn(Component, dump_only=True, many=True)
|
||||||
parent = NestedOn(Computer, dump_only=True, description=m.Event.parent_id.comment)
|
parent = NestedOn(Computer, dump_only=True, description=m.Event.parent_id.comment)
|
||||||
|
url = URL(dump_only=True, description=m.Event.url.__doc__)
|
||||||
|
|
||||||
|
|
||||||
class EventWithOneDevice(Event):
|
class EventWithOneDevice(Event):
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
from boltons import urlutils
|
||||||
from citext import CIText
|
from citext import CIText
|
||||||
from flask import g
|
from flask import g
|
||||||
from sqlalchemy import TEXT
|
from sqlalchemy import TEXT
|
||||||
|
@ -9,6 +10,7 @@ from sqlalchemy.sql import expression as exp
|
||||||
from sqlalchemy_utils import LtreeType
|
from sqlalchemy_utils import LtreeType
|
||||||
from sqlalchemy_utils.types.ltree import LQUERY
|
from sqlalchemy_utils.types.ltree import LQUERY
|
||||||
from teal.db import UUIDLtree
|
from teal.db import UUIDLtree
|
||||||
|
from teal.resource import url_for_resource
|
||||||
|
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.resources.device.models import Device
|
from ereuse_devicehub.resources.device.models import Device
|
||||||
|
@ -61,6 +63,11 @@ class Lot(Thing):
|
||||||
assert isinstance(child, uuid.UUID)
|
assert isinstance(child, uuid.UUID)
|
||||||
Path.delete(self.id, child)
|
Path.delete(self.id, child)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self) -> urlutils.URL:
|
||||||
|
"""The URL where to GET this event."""
|
||||||
|
return urlutils.URL(url_for_resource(Lot, item_id=self.id))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def children(self):
|
def children(self):
|
||||||
"""The children lots."""
|
"""The children lots."""
|
||||||
|
|
|
@ -3,6 +3,7 @@ from datetime import datetime
|
||||||
from typing import Iterable, Set, Union
|
from typing import Iterable, Set, Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
from boltons import urlutils
|
||||||
from sqlalchemy import Column
|
from sqlalchemy import Column
|
||||||
from sqlalchemy.orm import Query, relationship
|
from sqlalchemy.orm import Query, relationship
|
||||||
from sqlalchemy_utils import Ltree
|
from sqlalchemy_utils import Ltree
|
||||||
|
@ -46,6 +47,9 @@ class Lot(Thing):
|
||||||
def parents(self) -> LotQuery:
|
def parents(self) -> LotQuery:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self) -> urlutils.URL:
|
||||||
|
pass
|
||||||
|
|
||||||
class Path:
|
class Path:
|
||||||
id = ... # type: Column
|
id = ... # type: Column
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from marshmallow import fields as f
|
from marshmallow import fields as f
|
||||||
from teal.marshmallow import SanitizedStr
|
from teal.marshmallow import SanitizedStr, URL
|
||||||
|
|
||||||
from ereuse_devicehub.marshmallow import NestedOn
|
from ereuse_devicehub.marshmallow import NestedOn
|
||||||
from ereuse_devicehub.resources.device.schemas import Device
|
from ereuse_devicehub.resources.device.schemas import Device
|
||||||
|
@ -15,3 +15,4 @@ class Lot(Thing):
|
||||||
devices = NestedOn(Device, many=True, dump_only=True)
|
devices = NestedOn(Device, many=True, dump_only=True)
|
||||||
children = NestedOn('Lot', many=True, dump_only=True)
|
children = NestedOn('Lot', many=True, dump_only=True)
|
||||||
parents = NestedOn('Lot', many=True, dump_only=True)
|
parents = NestedOn('Lot', many=True, dump_only=True)
|
||||||
|
url = URL(dump_only=True, description=m.Lot.url.__doc__)
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from marshmallow import post_load
|
from marshmallow import post_load
|
||||||
from marshmallow.fields import DateTime, List, String, URL
|
from marshmallow.fields import DateTime, List, String
|
||||||
|
from teal.marshmallow import URL
|
||||||
from teal.resource import Schema
|
from teal.resource import Schema
|
||||||
|
|
||||||
from ereuse_devicehub.resources import models as m
|
from ereuse_devicehub.resources import models as m
|
||||||
|
@ -20,7 +21,6 @@ class UnitCodes(Enum):
|
||||||
|
|
||||||
class Thing(Schema):
|
class Thing(Schema):
|
||||||
type = String(description='Only required when it is nested.')
|
type = String(description='Only required when it is nested.')
|
||||||
url = URL(dump_only=True, description='The URL of the resource.')
|
|
||||||
same_as = List(URL(dump_only=True), dump_only=True, data_key='sameAs')
|
same_as = List(URL(dump_only=True), dump_only=True, data_key='sameAs')
|
||||||
updated = DateTime('iso', dump_only=True, description=m.Thing.updated.comment.strip())
|
updated = DateTime('iso', dump_only=True, description=m.Thing.updated.comment.strip())
|
||||||
created = DateTime('iso', dump_only=True, description=m.Thing.created.comment.strip())
|
created = DateTime('iso', dump_only=True, description=m.Thing.created.comment.strip())
|
||||||
|
|
|
@ -30,6 +30,7 @@ class TestConfig(DevicehubConfig):
|
||||||
TESTING = True
|
TESTING = True
|
||||||
ORGANIZATION_NAME = 'FooOrg'
|
ORGANIZATION_NAME = 'FooOrg'
|
||||||
ORGANIZATION_TAX_ID = 'foo-org-id'
|
ORGANIZATION_TAX_ID = 'foo-org-id'
|
||||||
|
SERVER_NAME = 'localhost'
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='session')
|
@pytest.fixture(scope='session')
|
||||||
|
|
|
@ -102,6 +102,8 @@ def test_device_query(user: UserClient):
|
||||||
"""Checks result of inventory."""
|
"""Checks result of inventory."""
|
||||||
user.post(conftest.file('basic.snapshot'), res=Snapshot)
|
user.post(conftest.file('basic.snapshot'), res=Snapshot)
|
||||||
i, _ = user.get(res=Device)
|
i, _ = user.get(res=Device)
|
||||||
|
assert i['url'] == '/devices/'
|
||||||
|
assert i['items'][0]['url'] == '/devices/1'
|
||||||
pc = next(d for d in i['items'] if d['type'] == 'Desktop')
|
pc = next(d for d in i['items'] if d['type'] == 'Desktop')
|
||||||
assert len(pc['events']) == 4
|
assert len(pc['events']) == 4
|
||||||
assert len(pc['components']) == 3
|
assert len(pc['components']) == 3
|
||||||
|
|
Reference in a new issue