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:
parent
d2d48280cb
commit
d172a0e756
|
@ -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.')
|
||||
|
|
|
@ -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()}')
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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__)
|
||||
|
|
|
@ -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(
|
||||
|
|
Reference in New Issue