diff --git a/ereuse_devicehub/resources/device/models.py b/ereuse_devicehub/resources/device/models.py index 6cca8424..5c11bdf8 100644 --- a/ereuse_devicehub/resources/device/models.py +++ b/ereuse_devicehub/resources/device/models.py @@ -5,6 +5,7 @@ from itertools import chain from operator import attrgetter from typing import Dict, List, Set +from boltons import urlutils from citext import CIText from ereuse_utils.naming import Naming 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, \ check_range from teal.marshmallow import ValidationError +from teal.resource import url_for_resource from ereuse_devicehub.db import db 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)) color = Column(ColorType) - color.comment = """ - - """ + color.comment = """The predominant color of the device.""" @property def events(self) -> list: @@ -93,6 +93,11 @@ class Device(Thing): and not getattr(c, 'foreign_keys', None) 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 def __mapper_args__(cls): """ diff --git a/ereuse_devicehub/resources/device/models.pyi b/ereuse_devicehub/resources/device/models.pyi index a5bcb688..b2138d78 100644 --- a/ereuse_devicehub/resources/device/models.pyi +++ b/ereuse_devicehub/resources/device/models.pyi @@ -1,5 +1,6 @@ from typing import Dict, List, Set +from boltons import urlutils from boltons.urlutils import URL from colour import Color from sqlalchemy import Column, Integer @@ -51,6 +52,9 @@ class Device(Thing): self.tags = ... # type: Set[Tag] self.lots = ... # type: Set[Lot] + @property + def url(self) -> urlutils.URL: + pass class DisplayMixin: technology = ... # type: Column diff --git a/ereuse_devicehub/resources/device/schemas.py b/ereuse_devicehub/resources/device/schemas.py index c065285b..03269f1e 100644 --- a/ereuse_devicehub/resources/device/schemas.py +++ b/ereuse_devicehub/resources/device/schemas.py @@ -29,6 +29,7 @@ class Device(Thing): 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_one = NestedOn('Event', many=True, load_only=True, collection_class=OrderedSet) + url = URL(dump_only=True, description=m.Device.url.__doc__) @pre_load def from_events_to_events_one(self, data: dict): diff --git a/ereuse_devicehub/resources/device/views.py b/ereuse_devicehub/resources/device/views.py index 642232b7..9aabcc76 100644 --- a/ereuse_devicehub/resources/device/views.py +++ b/ereuse_devicehub/resources/device/views.py @@ -114,7 +114,10 @@ class DeviceView(View): 'page': devices.page, 'perPage': devices.per_page, 'total': devices.total, - } + 'previous': devices.prev_num, + 'next': devices.next_num + }, + 'url': request.path } return jsonify(ret) diff --git a/ereuse_devicehub/resources/event/models.py b/ereuse_devicehub/resources/event/models.py index 198ea7b0..c34a78be 100644 --- a/ereuse_devicehub/resources/event/models.py +++ b/ereuse_devicehub/resources/event/models.py @@ -3,6 +3,7 @@ from datetime import datetime, timedelta from typing import Set, Union from uuid import uuid4 +from boltons import urlutils from citext import CIText from flask import current_app as app, g 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 from teal.enums import Country, Currency, Subdivision from teal.marshmallow import ValidationError +from teal.resource import url_for_resource from ereuse_devicehub.db import db 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. """ + @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 @declared_attr def __mapper_args__(cls): diff --git a/ereuse_devicehub/resources/event/models.pyi b/ereuse_devicehub/resources/event/models.pyi index 070ef28c..db3c3611 100644 --- a/ereuse_devicehub/resources/event/models.pyi +++ b/ereuse_devicehub/resources/event/models.pyi @@ -5,6 +5,7 @@ from distutils.version import StrictVersion from typing import Dict, List, Set, Union from uuid import UUID +from boltons import urlutils from boltons.urlutils import URL from sqlalchemy import Column from sqlalchemy.orm import relationship @@ -62,6 +63,9 @@ class Event(Thing): self.agent = ... # type: Agent self.author = ... # type: User + @property + def url(self) -> urlutils.URL: + pass class EventWithOneDevice(Event): diff --git a/ereuse_devicehub/resources/event/schemas.py b/ereuse_devicehub/resources/event/schemas.py index ad246617..9a143344 100644 --- a/ereuse_devicehub/resources/event/schemas.py +++ b/ereuse_devicehub/resources/event/schemas.py @@ -3,11 +3,11 @@ import decimal from flask import current_app as app from marshmallow import Schema as MarshmallowSchema, ValidationError, validates_schema from marshmallow.fields import Boolean, DateTime, Decimal, Float, Integer, List, Nested, String, \ - TimeDelta, URL, UUID + TimeDelta, UUID from marshmallow.validate import Length, Range from sqlalchemy.util import OrderedSet 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 ereuse_devicehub.marshmallow import NestedOn @@ -38,6 +38,7 @@ class Event(Thing): author = NestedOn(User, dump_only=True, exclude=('token',)) components = NestedOn(Component, dump_only=True, many=True) 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): diff --git a/ereuse_devicehub/resources/lot/models.py b/ereuse_devicehub/resources/lot/models.py index 678f91bf..24c2afe4 100644 --- a/ereuse_devicehub/resources/lot/models.py +++ b/ereuse_devicehub/resources/lot/models.py @@ -1,6 +1,7 @@ import uuid from datetime import datetime +from boltons import urlutils from citext import CIText from flask import g from sqlalchemy import TEXT @@ -9,6 +10,7 @@ from sqlalchemy.sql import expression as exp from sqlalchemy_utils import LtreeType from sqlalchemy_utils.types.ltree import LQUERY from teal.db import UUIDLtree +from teal.resource import url_for_resource from ereuse_devicehub.db import db from ereuse_devicehub.resources.device.models import Device @@ -61,6 +63,11 @@ class Lot(Thing): assert isinstance(child, uuid.UUID) 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 def children(self): """The children lots.""" diff --git a/ereuse_devicehub/resources/lot/models.pyi b/ereuse_devicehub/resources/lot/models.pyi index 1d10774e..39eac1e4 100644 --- a/ereuse_devicehub/resources/lot/models.pyi +++ b/ereuse_devicehub/resources/lot/models.pyi @@ -3,6 +3,7 @@ from datetime import datetime from typing import Iterable, 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 @@ -46,6 +47,9 @@ class Lot(Thing): def parents(self) -> LotQuery: pass + @property + def url(self) -> urlutils.URL: + pass class Path: id = ... # type: Column diff --git a/ereuse_devicehub/resources/lot/schemas.py b/ereuse_devicehub/resources/lot/schemas.py index 9bfa0fd1..e2eaefec 100644 --- a/ereuse_devicehub/resources/lot/schemas.py +++ b/ereuse_devicehub/resources/lot/schemas.py @@ -1,5 +1,5 @@ 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.resources.device.schemas import Device @@ -15,3 +15,4 @@ class Lot(Thing): devices = NestedOn(Device, many=True, dump_only=True) children = 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__) diff --git a/ereuse_devicehub/resources/schemas.py b/ereuse_devicehub/resources/schemas.py index 31601d18..0aec435a 100644 --- a/ereuse_devicehub/resources/schemas.py +++ b/ereuse_devicehub/resources/schemas.py @@ -1,7 +1,8 @@ from enum import Enum 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 ereuse_devicehub.resources import models as m @@ -20,7 +21,6 @@ class UnitCodes(Enum): class Thing(Schema): 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') updated = DateTime('iso', dump_only=True, description=m.Thing.updated.comment.strip()) created = DateTime('iso', dump_only=True, description=m.Thing.created.comment.strip()) diff --git a/tests/conftest.py b/tests/conftest.py index dd403ef0..17ba7756 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -30,6 +30,7 @@ class TestConfig(DevicehubConfig): TESTING = True ORGANIZATION_NAME = 'FooOrg' ORGANIZATION_TAX_ID = 'foo-org-id' + SERVER_NAME = 'localhost' @pytest.fixture(scope='session') diff --git a/tests/test_device_find.py b/tests/test_device_find.py index b53853b1..af73176e 100644 --- a/tests/test_device_find.py +++ b/tests/test_device_find.py @@ -102,6 +102,8 @@ def test_device_query(user: UserClient): """Checks result of inventory.""" user.post(conftest.file('basic.snapshot'), res=Snapshot) 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') assert len(pc['events']) == 4 assert len(pc['components']) == 3