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

View file

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

View file

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

View file

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

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

View file

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

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.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?

View file

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

View file

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

View file

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