Add things_response; create tags through ereuse Tag

This commit is contained in:
Xavier Bustamante Talavera 2019-01-26 12:49:31 +01:00
parent 9ec9fb0964
commit 74860be347
10 changed files with 108 additions and 37 deletions

View file

@ -111,4 +111,5 @@ class Devicehub(Teal):
def _prepare_request(self): def _prepare_request(self):
"""Prepares request stuff.""" """Prepares request stuff."""
inv = g.inventory = Inventory.current # type: Inventory 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))

View file

@ -1,3 +1,6 @@
from typing import Dict, List
from flask import Response, jsonify, request
from teal.query import NestedQueryFlaskParser from teal.query import NestedQueryFlaskParser
from webargs.flaskparser import FlaskParser from webargs.flaskparser import FlaskParser
@ -10,3 +13,31 @@ class SearchQueryParser(NestedQueryFlaskParser):
else: else:
v = super().parse_querystring(req, name, field) v = super().parse_querystring(req, name, field)
return v 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

View file

@ -11,7 +11,7 @@ from teal.resource import View
from ereuse_devicehub import auth from ereuse_devicehub import auth
from ereuse_devicehub.db import db 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 import search
from ereuse_devicehub.resources.device.models import Device, Manufacturer from ereuse_devicehub.resources.device.models import Device, Manufacturer
from ereuse_devicehub.resources.device.search import DeviceSearch from ereuse_devicehub.resources.device.search import DeviceSearch
@ -112,20 +112,10 @@ class DeviceView(View):
# Compute query # Compute query
query = self.query(args) query = self.query(args)
devices = query.paginate(page=args['page'], per_page=30) # type: Pagination devices = query.paginate(page=args['page'], per_page=30) # type: Pagination
ret = { return things_response(
'items': self.schema.dump(devices.items, many=True, nested=1), self.schema.dump(devices.items, many=True, nested=1),
# todo pagination should be in Header like github devices.page, devices.per_page, devices.total, devices.prev_num, devices.next_num
# 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)
def query(self, args): def query(self, args):
query = Device.query.distinct() # todo we should not force to do this if the query is ok query = Device.query.distinct() # todo we should not force to do this if the query is ok

View file

@ -10,6 +10,7 @@ from teal.marshmallow import EnumField
from teal.resource import View from teal.resource import View
from ereuse_devicehub.db import db 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.device.models import Device
from ereuse_devicehub.resources.lot.models import Lot, Path from ereuse_devicehub.resources.lot.models import Lot, Path
@ -78,17 +79,10 @@ class LotView(View):
if args['search']: if args['search']:
query = query.filter(Lot.name.ilike(args['search'] + '%')) query = query.filter(Lot.name.ilike(args['search'] + '%'))
lots = query.paginate(per_page=6 if args['search'] else 30) lots = query.paginate(per_page=6 if args['search'] else 30)
ret = { return things_response(
'items': self.schema.dump(lots.items, many=True, nested=0), self.schema.dump(lots.items, many=True, nested=0),
'pagination': { lots.page, lots.per_page, lots.total, lots.prev_num, lots.next_num
'page': lots.page, )
'perPage': lots.per_page,
'total': lots.total,
'previous': lots.prev_num,
'next': lots.next_num
},
'url': request.path
}
return jsonify(ret) return jsonify(ret)
def delete(self, id): def delete(self, id):

View file

@ -9,6 +9,7 @@ from teal.db import DB_CASCADE_SET_NULL, Query, URL, check_lower
from teal.marshmallow import ValidationError from teal.marshmallow import ValidationError
from teal.resource import url_for_resource 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.agent.models import Organization
from ereuse_devicehub.resources.device.models import Device from ereuse_devicehub.resources.device.models import Device
from ereuse_devicehub.resources.models import Thing from ereuse_devicehub.resources.models import Thing
@ -23,7 +24,7 @@ class Tags(Set['Tag']):
class Tag(Thing): 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.""" id.comment = """The ID of the tag."""
org_id = Column(UUID(as_uuid=True), org_id = Column(UUID(as_uuid=True),
ForeignKey(Organization.id), ForeignKey(Organization.id),
@ -49,7 +50,7 @@ class Tag(Thing):
backref=backref('tags', lazy=True, collection_class=Tags), backref=backref('tags', lazy=True, collection_class=Tags),
primaryjoin=Device.id == device_id) primaryjoin=Device.id == device_id)
"""The device linked to this tag.""" """The device linked to this tag."""
secondary = Column(Unicode(), check_lower('secondary'), index=True) secondary = Column(db.CIText(), index=True)
secondary.comment = """ secondary.comment = """
A secondary identifier for this tag. It has the same A secondary identifier for this tag. It has the same
constraints as the main one. Only needed in special cases. 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 Only tags that are from the default organization can be
printed by the user. 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: def __repr__(self) -> str:
return '<Tag {0.id} org:{0.org_id} device:{0.device_id}>'.format(self) return '<Tag {0.id} org:{0.org_id} device:{0.device_id}>'.format(self)

View file

@ -45,6 +45,10 @@ class Tag(Thing):
def printable(self) -> bool: def printable(self) -> bool:
pass pass
@classmethod
def is_printable_q(cls):
pass
@property @property
def url(self) -> urlutils.URL: def url(self) -> urlutils.URL:
pass pass

View file

@ -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.marshmallow import ValidationError
from teal.resource import View, url_for_resource from teal.resource import View, url_for_resource
from ereuse_devicehub.db import db 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.device.models import Device
from ereuse_devicehub.resources.tag import Tag from ereuse_devicehub.resources.tag import Tag
@ -17,14 +19,21 @@ class TagView(View):
res = self._post_one() res = self._post_one()
return res 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): def _create_many_regular_tags(self, num: int):
tags_id, _ = g.tag_provider.post('/', {}, query=[('num', num)]) 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] tags = [Tag(id=tag_id, provider=g.inventory.tag_provider) for tag_id in tags_id]
db.session.add_all(tags) db.session.add_all(tags)
db.session.commit() db.session.commit()
response = jsonify(items=self.schema.dump(tags, many=True, nested=1)) # type: Response return things_response(self.schema.dump(tags, many=True, nested=1), code=201)
response.status_code = 201
return response
def _post_one(self): def _post_one(self):
# todo do we use this? # todo do we use this?

View file

@ -5,7 +5,7 @@ click==6.7
click-spinner==0.1.8 click-spinner==0.1.8
colorama==0.3.9 colorama==0.3.9
colour==0.1.5 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==1.0.2
Flask-Cors==3.0.6 Flask-Cors==3.0.6
Flask-SQLAlchemy==2.3.2 Flask-SQLAlchemy==2.3.2

View file

@ -32,7 +32,7 @@ setup(
'teal>=0.2.0a35', # teal always first 'teal>=0.2.0a35', # teal always first
'click', 'click',
'click-spinner', 'click-spinner',
'ereuse-utils[naming, test, session, cli]>=0.4b14', 'ereuse-utils[naming, test, session, cli]>=0.4b15',
'hashids', 'hashids',
'marshmallow_enum', 'marshmallow_enum',
'psycopg2-binary', 'psycopg2-binary',

View file

@ -3,6 +3,7 @@ import pathlib
import pytest import pytest
import requests_mock import requests_mock
from boltons.urlutils import URL from boltons.urlutils import URL
from ereuse_utils.session import DevicehubClient
from pytest import raises from pytest import raises
from teal.db import MultipleResourcesFound, ResourceNotFound, UniqueViolation from teal.db import MultipleResourcesFound, ResourceNotFound, UniqueViolation
from teal.marshmallow import ValidationError 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/', requests_mock.post('https://example.com/',
# request # request
request_headers={ request_headers={
'Authorization': 'Basic 52dacef0-6bcb-4919-bfed-f10d2c96ecee' 'Authorization': 'Basic {}'.format(DevicehubClient.encode_token(
'52dacef0-6bcb-4919-bfed-f10d2c96ecee'))
}, },
# response # response
json=['tag1id', 'tag2id'], 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'][0]['printable'], 'Tags made this way are printable'
assert data['items'][1]['id'] == 'tag2id' assert data['items'][1]['id'] == 'tag2id'
assert data['items'][1]['printable'] 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'