diff --git a/ereuse_devicehub/client.py b/ereuse_devicehub/client.py
index b5304ed9..b7b38da1 100644
--- a/ereuse_devicehub/client.py
+++ b/ereuse_devicehub/client.py
@@ -106,7 +106,7 @@ class Client(TealClient):
def login(self, email: str, password: str):
assert isinstance(email, str)
assert isinstance(password, str)
- return self.post({'email': email, 'password': password}, '/users/login', status=200)
+ return self.post({'email': email, 'password': password}, '/users/login/', status=200)
def get_many(self,
res: ResourceLike,
diff --git a/ereuse_devicehub/resources/documents/templates/documents/erasure.html b/ereuse_devicehub/resources/documents/templates/documents/erasure.html
index 908bdbe8..29e1d138 100644
--- a/ereuse_devicehub/resources/documents/templates/documents/erasure.html
+++ b/ereuse_devicehub/resources/documents/templates/documents/erasure.html
@@ -16,9 +16,11 @@
{% for erasure in erasures %}
-
- {{ erasure.parent.serial_number.upper() }}
- |
+ {% if erasure.parent.serial_number %}
+
+ {{ erasure.parent.serial_number.upper() }}
+ |
+ {% endif %}
{{ erasure.parent.tags }}
|
diff --git a/ereuse_devicehub/resources/event/views.py b/ereuse_devicehub/resources/event/views.py
index 30f75a0a..570a4062 100644
--- a/ereuse_devicehub/resources/event/views.py
+++ b/ereuse_devicehub/resources/event/views.py
@@ -4,6 +4,7 @@ from uuid import UUID
from flask import current_app as app, request
from sqlalchemy.util import OrderedSet
+from teal.marshmallow import ValidationError
from teal.resource import View
from ereuse_devicehub.db import db
@@ -16,6 +17,8 @@ class EventView(View):
def post(self):
"""Posts an event."""
json = request.get_json(validate=False)
+ if 'type' not in json:
+ raise ValidationError('Resource needs a type.')
e = app.resources[json['type']].schema.load(json)
Model = db.Model._decl_class_registry.data[json['type']]()
event = Model(**e)
diff --git a/ereuse_devicehub/resources/tag/model.py b/ereuse_devicehub/resources/tag/model.py
index 85b7d68e..5238d846 100644
--- a/ereuse_devicehub/resources/tag/model.py
+++ b/ereuse_devicehub/resources/tag/model.py
@@ -1,11 +1,13 @@
from contextlib import suppress
from typing import Set
+from boltons import urlutils
from sqlalchemy import BigInteger, Column, ForeignKey, Unicode, UniqueConstraint
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import backref, relationship, validates
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.resources.agent.models import Organization
from ereuse_devicehub.resources.device.models import Device
@@ -93,6 +95,21 @@ class Tag(Thing):
def type(self) -> str:
return self.__class__.__name__
+ @property
+ def url(self) -> urlutils.URL:
+ """The URL where to GET this device."""
+ # todo this url only works for printable internal tags
+ return urlutils.URL(url_for_resource(Tag, item_id=self.id))
+
+ @property
+ def printable(self) -> bool:
+ """Can the tag be printed by the user?
+
+ Only tags that are from the default organization can be
+ printed by the user.
+ """
+ return Organization.get_default_org_id == self.org_id
+
def __repr__(self) -> str:
return ''.format(self)
diff --git a/ereuse_devicehub/resources/tag/model.pyi b/ereuse_devicehub/resources/tag/model.pyi
index 96082341..8e365551 100644
--- a/ereuse_devicehub/resources/tag/model.pyi
+++ b/ereuse_devicehub/resources/tag/model.pyi
@@ -1,5 +1,6 @@
from uuid import UUID
+from boltons import urlutils
from boltons.urlutils import URL
from sqlalchemy import Column
from sqlalchemy.orm import relationship
@@ -39,3 +40,11 @@ class Tag(Thing):
def like_etag(self) -> bool:
pass
+
+ @property
+ def printable(self) -> bool:
+ pass
+
+ @property
+ def url(self) -> urlutils.URL:
+ pass
diff --git a/ereuse_devicehub/resources/tag/schema.py b/ereuse_devicehub/resources/tag/schema.py
index f41532df..22a492d5 100644
--- a/ereuse_devicehub/resources/tag/schema.py
+++ b/ereuse_devicehub/resources/tag/schema.py
@@ -1,4 +1,5 @@
from sqlalchemy.util import OrderedSet
+from marshmallow.fields import Boolean
from teal.marshmallow import SanitizedStr, URL
from ereuse_devicehub.marshmallow import NestedOn
@@ -23,3 +24,5 @@ class Tag(Thing):
device = NestedOn(Device, dump_only=True)
org = NestedOn(Organization, collection_class=OrderedSet, only_query='id')
secondary = SanitizedStr(lower=True, description=m.Tag.secondary.comment)
+ printable = Boolean(dump_only=True, decsription=m.Tag.printable.__doc__)
+ url = URL(dump_only=True, description=m.Tag.url.__doc__)
diff --git a/ereuse_devicehub/resources/user/__init__.py b/ereuse_devicehub/resources/user/__init__.py
index 77ad9677..f3c4b61f 100644
--- a/ereuse_devicehub/resources/user/__init__.py
+++ b/ereuse_devicehub/resources/user/__init__.py
@@ -20,7 +20,7 @@ class UserDef(Resource):
cli_commands = ((self.create_user, 'create-user'),)
super().__init__(app, import_name, static_folder, static_url_path, template_folder,
url_prefix, subdomain, url_defaults, root_path, cli_commands)
- self.add_url_rule('/login', view_func=login, methods={'POST'})
+ self.add_url_rule('/login/', view_func=login, methods={'POST'})
@argument('email')
@option('-a', '--agent', help='The name of an agent to create with the user.')
diff --git a/setup.py b/setup.py
index 8f6c261b..70e82c27 100644
--- a/setup.py
+++ b/setup.py
@@ -12,7 +12,7 @@ test_requires = [
setup(
name='ereuse-devicehub',
- version='0.2.0b2',
+ version='0.2.0b3',
url='https://github.com/ereuse/devicehub-teal',
project_urls=OrderedDict((
('Documentation', 'http://devicheub.ereuse.org'),
diff --git a/tests/test_basic.py b/tests/test_basic.py
index debb4c64..7fc1a29c 100644
--- a/tests/test_basic.py
+++ b/tests/test_basic.py
@@ -22,7 +22,7 @@ def test_api_docs(client: Client):
'/devices/',
'/tags/',
'/snapshots/',
- '/users/login',
+ '/users/login/',
'/events/',
'/lots/',
'/manufacturers/',
diff --git a/tests/test_user.py b/tests/test_user.py
index 7a8410b5..aab20746 100644
--- a/tests/test_user.py
+++ b/tests/test_user.py
@@ -74,7 +74,7 @@ def test_login_success(client: Client, app: Devicehub):
with app.app_context():
create_user()
user, _ = client.post({'email': 'foo@foo.com', 'password': 'foo'},
- uri='/users/login',
+ uri='/users/login/',
status=200)
assert user['email'] == 'foo@foo.com'
assert UUID(b64decode(user['token'].encode()).decode()[:-1])
@@ -90,12 +90,12 @@ def test_login_failure(client: Client, app: Devicehub):
with app.app_context():
create_user()
client.post({'email': 'foo@foo.com', 'password': 'wrong pass'},
- uri='/users/login',
+ uri='/users/login/',
status=WrongCredentials)
# Wrong URI
client.post({}, uri='/wrong-uri', status=NotFound)
# Malformed data
- client.post({}, uri='/users/login', status=ValidationError)
+ client.post({}, uri='/users/login/', status=ValidationError)
client.post({'email': 'this is not an email', 'password': 'nope'},
- uri='/users/login',
+ uri='/users/login/',
status=ValidationError)