Update pagination and return URL for Lot, Device, Event resources

This commit is contained in:
Xavier Bustamante Talavera 2018-10-05 17:13:23 +02:00
parent d1a23cbf9d
commit 4f0493c464
13 changed files with 49 additions and 9 deletions

View File

@ -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):
""" """

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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):

View File

@ -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):

View File

@ -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):

View File

@ -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."""

View File

@ -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

View File

@ -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__)

View File

@ -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())

View File

@ -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')

View File

@ -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