Tag-User relationship (#43)

* Add owner_id reference in tag model and related migration

* Add owner param to views and cli commands and schema

* Create tag which belong to an owner from dummy script
This commit is contained in:
fedjo 2020-07-07 14:58:55 +02:00 committed by GitHub
parent d2d48280cb
commit d172a0e756
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 64 additions and 5 deletions

View File

@ -77,10 +77,12 @@ class Dummy:
runner.invoke('tag', 'add', id,
'-p', 'https://t.devicetag.io',
'-s', sec,
'-u', user1.user["id"],
'-o', org_id)
# create tag for pc-laudem
runner.invoke('tag', 'add', 'tagA',
'-p', 'https://t.devicetag.io',
'-u', user1.user["id"],
'-s', 'tagA-secondary')
files = tuple(Path(__file__).parent.joinpath('files').iterdir())
print('done.')
@ -144,7 +146,7 @@ class Dummy:
res=Lot,
item='{}/devices'.format(lot_user3['id']),
query=[('id', pc) for pc in itertools.islice(pcs, 11, 14)])
lot4, _ = user4.post({},
res=Lot,
item='{}/devices'.format(lot_user4['id']),

View File

@ -0,0 +1,41 @@
"""Owner in tags
Revision ID: b9b0ee7d9dca
Revises: 151253ac5c55
Create Date: 2020-06-30 17:41:28.611314
"""
from alembic import op
from alembic import context
import sqlalchemy as sa
import sqlalchemy_utils
from sqlalchemy.dialects import postgresql
import citext
import teal
# revision identifiers, used by Alembic.
revision = 'b9b0ee7d9dca'
down_revision = 'fbb7e2a0cde0'
branch_labels = None
depends_on = None
def get_inv():
INV = context.get_x_argument(as_dictionary=True).get('inventory')
if not INV:
raise ValueError("Inventory value is not specified")
return INV
def upgrade():
op.add_column('tag', sa.Column('owner_id', postgresql.UUID(), nullable=True), schema=f'{get_inv()}')
op.create_foreign_key("fk_tag_owner_id_user_id",
"tag", "user",
["owner_id"], ["id"],
ondelete="SET NULL",
source_schema=f'{get_inv()}', referent_schema='common')
def downgrade():
op.drop_constraint("fk_tag_owner_id_user_id", "tag", type_="foreignkey", schema=f'{get_inv()}')
op.drop_column('tag', 'owner_id', schema=f'{get_inv()}')

View File

@ -18,6 +18,7 @@ class TagDef(Resource):
VIEW = TagView
ID_CONVERTER = Converters.lower
OWNER_H = 'The id of the user who owns this tag. '
ORG_H = 'The name of an existing organization in the DB. '
'By default the organization operating this Devicehub.'
PROV_H = 'The Base URL of the provider; scheme + domain. Ex: "https://foo.com". '
@ -48,6 +49,7 @@ class TagDef(Resource):
view_func=device_view,
methods={'PUT'})
@option('-u', '--owner', help=OWNER_H)
@option('-o', '--org', help=ORG_H)
@option('-p', '--provider', help=PROV_H)
@option('-s', '--sec', help=Tag.secondary.comment)
@ -55,18 +57,19 @@ class TagDef(Resource):
def create_tag(self,
id: str,
org: str = None,
owner: str = None,
sec: str = None,
provider: str = None):
"""Create a tag with the given ID."""
db.session.add(Tag(**self.schema.load(
dict(id=id, org=org, secondary=sec, provider=provider)
dict(id=id, owner=owner, org=org, secondary=sec, provider=provider)
)))
db.session.commit()
@option('--org', help=ORG_H)
@option('--provider', help=PROV_H)
@argument('path', type=cli.Path(writable=True))
def create_tags_csv(self, path: pathlib.Path, org: str, provider: str):
def create_tags_csv(self, path: pathlib.Path, owner: str, org: str, provider: str):
"""Creates tags by reading CSV from ereuse-tag.
CSV must have the following columns:
@ -77,6 +80,6 @@ class TagDef(Resource):
with path.open() as f:
for id, sec in csv.reader(f):
db.session.add(Tag(**self.schema.load(
dict(id=id, org=org, secondary=sec, provider=provider)
dict(id=id, owner=owner, org=org, secondary=sec, provider=provider)
)))
db.session.commit()

View File

@ -1,6 +1,7 @@
from contextlib import suppress
from typing import Set
from flask import g
from boltons import urlutils
from sqlalchemy import BigInteger, Column, ForeignKey, UniqueConstraint
from sqlalchemy.dialects.postgresql import UUID
@ -12,6 +13,7 @@ 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.user.models import User
from ereuse_devicehub.resources.models import Thing
@ -26,6 +28,11 @@ class Tags(Set['Tag']):
class Tag(Thing):
id = Column(db.CIText(), primary_key=True)
id.comment = """The ID of the tag."""
owner_id = Column(UUID(as_uuid=True),
ForeignKey(User.id),
nullable=False,
default=lambda: g.user.id)
owner = relationship(User, primaryjoin=owner_id == User.id)
org_id = Column(UUID(as_uuid=True),
ForeignKey(Organization.id),
primary_key=True,
@ -50,7 +57,7 @@ class Tag(Thing):
primaryjoin=Device.id == device_id)
"""The device linked to this tag."""
secondary = Column(db.CIText(), index=True)
secondary.comment = """A secondary identifier for this tag.
secondary.comment = """A secondary identifier for this tag.
It has the same constraints as the main one. Only needed in special cases.
"""

View File

@ -3,6 +3,7 @@ from sqlalchemy.util import OrderedSet
from teal.marshmallow import SanitizedStr, URL
from ereuse_devicehub.marshmallow import NestedOn
from ereuse_devicehub.resources.user.schemas import User
from ereuse_devicehub.resources.agent.schemas import Organization
from ereuse_devicehub.resources.device.schemas import Device
from ereuse_devicehub.resources.schemas import Thing
@ -22,6 +23,7 @@ class Tag(Thing):
provider = URL(description=m.Tag.provider.comment,
validator=without_slash)
device = NestedOn(Device, dump_only=True)
owner = NestedOn(User, only_query='id')
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__)

View File

@ -4,12 +4,14 @@ from teal.marshmallow import ValidationError
from teal.resource import View, url_for_resource
from ereuse_devicehub.db import db
from ereuse_devicehub import auth
from ereuse_devicehub.query import things_response
from ereuse_devicehub.resources.device.models import Device
from ereuse_devicehub.resources.tag import Tag
class TagView(View):
@auth.Auth.requires_auth
def post(self):
"""Creates a tag."""
num = request.args.get('num', type=int)
@ -19,8 +21,10 @@ class TagView(View):
res = self._post_one()
return res
@auth.Auth.requires_auth
def find(self, args: dict):
tags = Tag.query.filter(Tag.is_printable_q()) \
.filter_by(owner=g.user) \
.order_by(Tag.created.desc()) \
.paginate(per_page=200) # type: Pagination
return things_response(