Add things_response; create tags through ereuse Tag
This commit is contained in:
parent
9ec9fb0964
commit
74860be347
|
@ -111,4 +111,5 @@ class Devicehub(Teal):
|
|||
def _prepare_request(self):
|
||||
"""Prepares request stuff."""
|
||||
inv = g.inventory = Inventory.current # type: Inventory
|
||||
g.tag_provider = DevicehubClient(base_url=inv.tag_provider, token=inv.tag_token)
|
||||
g.tag_provider = DevicehubClient(base_url=inv.tag_provider,
|
||||
token=DevicehubClient.encode_token(inv.tag_token))
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
from typing import Dict, List
|
||||
|
||||
from flask import Response, jsonify, request
|
||||
from teal.query import NestedQueryFlaskParser
|
||||
from webargs.flaskparser import FlaskParser
|
||||
|
||||
|
@ -10,3 +13,31 @@ class SearchQueryParser(NestedQueryFlaskParser):
|
|||
else:
|
||||
v = super().parse_querystring(req, name, field)
|
||||
return v
|
||||
|
||||
|
||||
def things_response(items: List[Dict],
|
||||
page: int = None,
|
||||
per_page: int = None,
|
||||
total: int = None,
|
||||
previous: int = None,
|
||||
next: int = None,
|
||||
url: str = None,
|
||||
code: int = 200) -> Response:
|
||||
"""Generates a Devicehub API list conformant response for multiple
|
||||
things.
|
||||
"""
|
||||
response = jsonify({
|
||||
'items': items,
|
||||
# todo pagination should be in Header like github
|
||||
# https://developer.github.com/v3/guides/traversing-with-pagination/
|
||||
'pagination': {
|
||||
'page': page,
|
||||
'perPage': per_page,
|
||||
'total': total,
|
||||
'previous': previous,
|
||||
'next': next
|
||||
},
|
||||
'url': url or request.path
|
||||
})
|
||||
response.status_code = code
|
||||
return response
|
||||
|
|
|
@ -11,7 +11,7 @@ from teal.resource import View
|
|||
|
||||
from ereuse_devicehub import auth
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.query import SearchQueryParser
|
||||
from ereuse_devicehub.query import SearchQueryParser, things_response
|
||||
from ereuse_devicehub.resources import search
|
||||
from ereuse_devicehub.resources.device.models import Device, Manufacturer
|
||||
from ereuse_devicehub.resources.device.search import DeviceSearch
|
||||
|
@ -112,20 +112,10 @@ class DeviceView(View):
|
|||
# Compute query
|
||||
query = self.query(args)
|
||||
devices = query.paginate(page=args['page'], per_page=30) # type: Pagination
|
||||
ret = {
|
||||
'items': self.schema.dump(devices.items, many=True, nested=1),
|
||||
# todo pagination should be in Header like github
|
||||
# https://developer.github.com/v3/guides/traversing-with-pagination/
|
||||
'pagination': {
|
||||
'page': devices.page,
|
||||
'perPage': devices.per_page,
|
||||
'total': devices.total,
|
||||
'previous': devices.prev_num,
|
||||
'next': devices.next_num
|
||||
},
|
||||
'url': request.path
|
||||
}
|
||||
return jsonify(ret)
|
||||
return things_response(
|
||||
self.schema.dump(devices.items, many=True, nested=1),
|
||||
devices.page, devices.per_page, devices.total, devices.prev_num, devices.next_num
|
||||
)
|
||||
|
||||
def query(self, args):
|
||||
query = Device.query.distinct() # todo we should not force to do this if the query is ok
|
||||
|
|
|
@ -10,6 +10,7 @@ from teal.marshmallow import EnumField
|
|||
from teal.resource import View
|
||||
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.query import things_response
|
||||
from ereuse_devicehub.resources.device.models import Device
|
||||
from ereuse_devicehub.resources.lot.models import Lot, Path
|
||||
|
||||
|
@ -78,17 +79,10 @@ class LotView(View):
|
|||
if args['search']:
|
||||
query = query.filter(Lot.name.ilike(args['search'] + '%'))
|
||||
lots = query.paginate(per_page=6 if args['search'] else 30)
|
||||
ret = {
|
||||
'items': self.schema.dump(lots.items, many=True, nested=0),
|
||||
'pagination': {
|
||||
'page': lots.page,
|
||||
'perPage': lots.per_page,
|
||||
'total': lots.total,
|
||||
'previous': lots.prev_num,
|
||||
'next': lots.next_num
|
||||
},
|
||||
'url': request.path
|
||||
}
|
||||
return things_response(
|
||||
self.schema.dump(lots.items, many=True, nested=0),
|
||||
lots.page, lots.per_page, lots.total, lots.prev_num, lots.next_num
|
||||
)
|
||||
return jsonify(ret)
|
||||
|
||||
def delete(self, id):
|
||||
|
|
|
@ -9,6 +9,7 @@ from teal.db import DB_CASCADE_SET_NULL, Query, URL, check_lower
|
|||
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 Organization
|
||||
from ereuse_devicehub.resources.device.models import Device
|
||||
from ereuse_devicehub.resources.models import Thing
|
||||
|
@ -23,7 +24,7 @@ class Tags(Set['Tag']):
|
|||
|
||||
|
||||
class Tag(Thing):
|
||||
id = Column(Unicode(), check_lower('id'), primary_key=True)
|
||||
id = Column(db.CIText(), primary_key=True)
|
||||
id.comment = """The ID of the tag."""
|
||||
org_id = Column(UUID(as_uuid=True),
|
||||
ForeignKey(Organization.id),
|
||||
|
@ -49,7 +50,7 @@ class Tag(Thing):
|
|||
backref=backref('tags', lazy=True, collection_class=Tags),
|
||||
primaryjoin=Device.id == device_id)
|
||||
"""The device linked to this tag."""
|
||||
secondary = Column(Unicode(), check_lower('secondary'), index=True)
|
||||
secondary = Column(db.CIText(), index=True)
|
||||
secondary.comment = """
|
||||
A secondary identifier for this tag. It has the same
|
||||
constraints as the main one. Only needed in special cases.
|
||||
|
@ -108,7 +109,12 @@ class Tag(Thing):
|
|||
Only tags that are from the default organization can be
|
||||
printed by the user.
|
||||
"""
|
||||
return Organization.get_default_org_id() == self.org_id
|
||||
return self.org_id == Organization.get_default_org_id()
|
||||
|
||||
@classmethod
|
||||
def is_printable_q(cls):
|
||||
"""Return a SQLAlchemy filter expression for printable queries"""
|
||||
return cls.org_id == Organization.get_default_org_id()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return '<Tag {0.id} org:{0.org_id} device:{0.device_id}>'.format(self)
|
||||
|
|
|
@ -45,6 +45,10 @@ class Tag(Thing):
|
|||
def printable(self) -> bool:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def is_printable_q(cls):
|
||||
pass
|
||||
|
||||
@property
|
||||
def url(self) -> urlutils.URL:
|
||||
pass
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
from flask import Response, current_app as app, g, jsonify, redirect, request
|
||||
from flask import Response, current_app as app, g, redirect, request
|
||||
from flask_sqlalchemy import Pagination
|
||||
from teal.marshmallow import ValidationError
|
||||
from teal.resource import View, url_for_resource
|
||||
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.query import things_response
|
||||
from ereuse_devicehub.resources.device.models import Device
|
||||
from ereuse_devicehub.resources.tag import Tag
|
||||
|
||||
|
@ -17,14 +19,21 @@ class TagView(View):
|
|||
res = self._post_one()
|
||||
return res
|
||||
|
||||
def find(self, args: dict):
|
||||
tags = Tag.query.filter(Tag.is_printable_q()) \
|
||||
.order_by(Tag.created.desc()) \
|
||||
.paginate(per_page=200) # type: Pagination
|
||||
return things_response(
|
||||
self.schema.dump(tags.items, many=True, nested=0),
|
||||
tags.page, tags.per_page, tags.total, tags.prev_num, tags.next_num
|
||||
)
|
||||
|
||||
def _create_many_regular_tags(self, num: int):
|
||||
tags_id, _ = g.tag_provider.post('/', {}, query=[('num', num)])
|
||||
tags = [Tag(id=tag_id, provider=g.inventory.tag_provider) for tag_id in tags_id]
|
||||
db.session.add_all(tags)
|
||||
db.session.commit()
|
||||
response = jsonify(items=self.schema.dump(tags, many=True, nested=1)) # type: Response
|
||||
response.status_code = 201
|
||||
return response
|
||||
return things_response(self.schema.dump(tags, many=True, nested=1), code=201)
|
||||
|
||||
def _post_one(self):
|
||||
# todo do we use this?
|
||||
|
|
|
@ -5,7 +5,7 @@ click==6.7
|
|||
click-spinner==0.1.8
|
||||
colorama==0.3.9
|
||||
colour==0.1.5
|
||||
ereuse-utils[naming, test, session, cli]==0.4.0b14
|
||||
ereuse-utils[naming, test, session, cli]==0.4.0b15
|
||||
Flask==1.0.2
|
||||
Flask-Cors==3.0.6
|
||||
Flask-SQLAlchemy==2.3.2
|
||||
|
|
2
setup.py
2
setup.py
|
@ -32,7 +32,7 @@ setup(
|
|||
'teal>=0.2.0a35', # teal always first
|
||||
'click',
|
||||
'click-spinner',
|
||||
'ereuse-utils[naming, test, session, cli]>=0.4b14',
|
||||
'ereuse-utils[naming, test, session, cli]>=0.4b15',
|
||||
'hashids',
|
||||
'marshmallow_enum',
|
||||
'psycopg2-binary',
|
||||
|
|
|
@ -3,6 +3,7 @@ import pathlib
|
|||
import pytest
|
||||
import requests_mock
|
||||
from boltons.urlutils import URL
|
||||
from ereuse_utils.session import DevicehubClient
|
||||
from pytest import raises
|
||||
from teal.db import MultipleResourcesFound, ResourceNotFound, UniqueViolation
|
||||
from teal.marshmallow import ValidationError
|
||||
|
@ -242,7 +243,8 @@ def test_crate_num_regular_tags(user: UserClient, requests_mock: requests_mock.m
|
|||
requests_mock.post('https://example.com/',
|
||||
# request
|
||||
request_headers={
|
||||
'Authorization': 'Basic 52dacef0-6bcb-4919-bfed-f10d2c96ecee'
|
||||
'Authorization': 'Basic {}'.format(DevicehubClient.encode_token(
|
||||
'52dacef0-6bcb-4919-bfed-f10d2c96ecee'))
|
||||
},
|
||||
# response
|
||||
json=['tag1id', 'tag2id'],
|
||||
|
@ -252,3 +254,37 @@ def test_crate_num_regular_tags(user: UserClient, requests_mock: requests_mock.m
|
|||
assert data['items'][0]['printable'], 'Tags made this way are printable'
|
||||
assert data['items'][1]['id'] == 'tag2id'
|
||||
assert data['items'][1]['printable']
|
||||
|
||||
|
||||
def test_get_tags_endpoint(user: UserClient, app: Devicehub,
|
||||
requests_mock: requests_mock.mocker.Mocker):
|
||||
"""Performs GET /tags after creating 3 tags, 2 printable and one
|
||||
not. Only the printable ones are returned.
|
||||
"""
|
||||
# Prepare test
|
||||
with app.app_context():
|
||||
org = Organization(name='bar', tax_id='bartax')
|
||||
tag = Tag(id='bar-1', org=org, provider=URL('http://foo.bar'))
|
||||
db.session.add(tag)
|
||||
db.session.commit()
|
||||
assert not tag.printable
|
||||
|
||||
requests_mock.post('https://example.com/',
|
||||
# request
|
||||
request_headers={
|
||||
'Authorization': 'Basic {}'.format(DevicehubClient.encode_token(
|
||||
'52dacef0-6bcb-4919-bfed-f10d2c96ecee'))
|
||||
},
|
||||
# response
|
||||
json=['tag1id', 'tag2id'],
|
||||
status_code=201)
|
||||
user.post({}, res=Tag, query=[('num', 2)])
|
||||
|
||||
# Test itself
|
||||
data, _ = user.get(res=Tag)
|
||||
assert len(data['items']) == 2, 'Only 2 tags are printable, thus retreived'
|
||||
# Order is created descending
|
||||
assert data['items'][0]['id'] == 'tag2id'
|
||||
assert data['items'][0]['printable']
|
||||
assert data['items'][1]['id'] == 'tag1id'
|
||||
assert data['items'][1]['printable'], 'Tags made this way are printable'
|
||||
|
|
Reference in New Issue