From 9272674760c9da47d568d3f53a2be0c2dee87869 Mon Sep 17 00:00:00 2001 From: Xavier Bustamante Talavera Date: Mon, 21 Jan 2019 16:08:55 +0100 Subject: [PATCH] Add inventories --- ereuse_devicehub/config.py | 11 ++++++++--- ereuse_devicehub/db.py | 6 +++++- ereuse_devicehub/devicehub.py | 8 +++++--- ereuse_devicehub/resources/agent/__init__.py | 2 +- ereuse_devicehub/resources/device/definitions.py | 5 +++-- ereuse_devicehub/resources/inventory/__init__.py | 0 ereuse_devicehub/resources/inventory/model.py | 12 ++++++++++++ ereuse_devicehub/resources/lot/__init__.py | 2 +- ereuse_devicehub/resources/user/models.py | 13 +++++++++++++ requirements.txt | 2 +- setup.py | 2 +- 11 files changed, 50 insertions(+), 13 deletions(-) create mode 100644 ereuse_devicehub/resources/inventory/__init__.py create mode 100644 ereuse_devicehub/resources/inventory/model.py diff --git a/ereuse_devicehub/config.py b/ereuse_devicehub/config.py index 029ca1c2..81cde720 100644 --- a/ereuse_devicehub/config.py +++ b/ereuse_devicehub/config.py @@ -64,10 +64,15 @@ class DevicehubConfig(Config): TAG_TOKEN = None """Access to the tag provider.""" - def __init__(self, db: str = None) -> None: + def __init__(self, schema: str = None, token=None) -> None: if not self.ORGANIZATION_NAME or not self.ORGANIZATION_TAX_ID: raise ValueError('You need to set the main organization parameters.') - if not self.TAG_BASE_URL or not self.TAG_TOKEN: + if not self.TAG_BASE_URL: raise ValueError('You need a tag service.') + self.TAG_TOKEN = token or self.TAG_TOKEN + if not self.TAG_TOKEN: + raise ValueError('You need a tag token') self.TAG_BASE_URL = boltons.urlutils.URL(self.TAG_BASE_URL) - super().__init__(db) + if schema: + self.SCHEMA = schema + super().__init__() diff --git a/ereuse_devicehub/db.py b/ereuse_devicehub/db.py index 6e17ad7b..c8b4e9ca 100644 --- a/ereuse_devicehub/db.py +++ b/ereuse_devicehub/db.py @@ -1,3 +1,4 @@ +import citext from sqlalchemy import event from sqlalchemy.dialects import postgresql from sqlalchemy.sql import expression @@ -11,7 +12,10 @@ class SQLAlchemy(SchemaSQLAlchemy): schema of the database, as it is in the `search_path` defined in teal. """ + # todo add here all types of columns used so we don't have to + # manually import them all the time UUID = postgresql.UUID + CIText = citext.CIText def drop_all(self, bind='__all__', app=None): """A faster nuke-like option to drop everything.""" @@ -37,6 +41,6 @@ def create_view(name, selectable): return table -db = SQLAlchemy(session_options={"autoflush": False}) +db = SQLAlchemy(session_options={'autoflush': False}) f = db.func exp = expression diff --git a/ereuse_devicehub/devicehub.py b/ereuse_devicehub/devicehub.py index 43e93648..5fe370ed 100644 --- a/ereuse_devicehub/devicehub.py +++ b/ereuse_devicehub/devicehub.py @@ -44,9 +44,11 @@ class Devicehub(Teal): # todo can I make it with a global Session only? event.listen(db.session, 'before_commit', DeviceSearch.update_modified_devices) - def _init_db(self): - super()._init_db() - DeviceSearch.set_all_devices_tokens_if_empty(self.db.session) + def _init_db(self, exclude_schema=None, check=False): + created = super()._init_db(exclude_schema, check) + if created: + DeviceSearch.set_all_devices_tokens_if_empty(self.db.session) + return created def regenerate_search(self): """Re-creates from 0 all the search tables.""" diff --git a/ereuse_devicehub/resources/agent/__init__.py b/ereuse_devicehub/resources/agent/__init__.py index dc92e1d4..0914bfb4 100644 --- a/ereuse_devicehub/resources/agent/__init__.py +++ b/ereuse_devicehub/resources/agent/__init__.py @@ -46,7 +46,7 @@ class OrganizationDef(AgentDef): print(json.dumps(o, indent=2)) return o - def init_db(self, db: SQLAlchemy): + def init_db(self, db: SQLAlchemy, exclude_schema=None): """Creates the default organization.""" org = models.Organization(**app.config.get_namespace('ORGANIZATION_')) db.session.add(org) diff --git a/ereuse_devicehub/resources/device/definitions.py b/ereuse_devicehub/resources/device/definitions.py index 8bac5f97..ef088bec 100644 --- a/ereuse_devicehub/resources/device/definitions.py +++ b/ereuse_devicehub/resources/device/definitions.py @@ -297,6 +297,7 @@ class ManufacturerDef(Resource): SCHEMA = schemas.Manufacturer AUTH = True - def init_db(self, db: 'db.SQLAlchemy'): + def init_db(self, db: 'db.SQLAlchemy', exclude_schema=None): """Loads the manufacturers to the database.""" - Manufacturer.add_all_to_session(db.session) + if exclude_schema != 'common': + Manufacturer.add_all_to_session(db.session) diff --git a/ereuse_devicehub/resources/inventory/__init__.py b/ereuse_devicehub/resources/inventory/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ereuse_devicehub/resources/inventory/model.py b/ereuse_devicehub/resources/inventory/model.py new file mode 100644 index 00000000..b1c583ed --- /dev/null +++ b/ereuse_devicehub/resources/inventory/model.py @@ -0,0 +1,12 @@ +from ereuse_devicehub.db import db +from ereuse_devicehub.resources.models import Thing + + +class Inventory(Thing): + __table_args__ = {'schema': 'common'} + id = db.Column(db.Unicode(), primary_key=True) + id.comment = """The name of the inventory as in the URL and schema.""" + name = db.Column(db.CIText(), nullable=False, unique=True) + name.comment = """The human name of the inventory.""" + tag_token = db.Column(db.UUID(as_uuid=True), unique=True) + tag_token.comment = """The token to access a Tag service.""" diff --git a/ereuse_devicehub/resources/lot/__init__.py b/ereuse_devicehub/resources/lot/__init__.py index b1b74cc5..d76bc54b 100644 --- a/ereuse_devicehub/resources/lot/__init__.py +++ b/ereuse_devicehub/resources/lot/__init__.py @@ -34,7 +34,7 @@ class LotDef(Resource): view_func=lot_device, methods={'POST', 'DELETE'}) - def init_db(self, db: 'db.SQLAlchemy'): + def init_db(self, db: 'db.SQLAlchemy', exclude_schema=None): # Create functions with pathlib.Path(__file__).parent.joinpath('dag.sql').open() as f: sql = f.read() diff --git a/ereuse_devicehub/resources/user/models.py b/ereuse_devicehub/resources/user/models.py index ac3a05da..26e641b2 100644 --- a/ereuse_devicehub/resources/user/models.py +++ b/ereuse_devicehub/resources/user/models.py @@ -5,6 +5,8 @@ from sqlalchemy import Column from sqlalchemy.dialects.postgresql import UUID from sqlalchemy_utils import EmailType, PasswordType +from ereuse_devicehub.db import db +from ereuse_devicehub.resources.inventory.model import Inventory from ereuse_devicehub.resources.models import STR_SIZE, Thing @@ -23,6 +25,10 @@ class User(Thing): data_types.html#module-sqlalchemy_utils.types.password>`_ """ token = Column(UUID(as_uuid=True), default=uuid4, unique=True) + inventories = db.relationship(Inventory, + backref=db.backref('users', lazy=True, collection_class=set), + secondary=lambda: UserInventory.__table__, + collection_class=set) def __repr__(self) -> str: return ''.format(self) @@ -31,3 +37,10 @@ class User(Thing): def individual(self): """The individual associated for this database, or None.""" return next(iter(self.individuals), None) + + +class UserInventory(db.Model): + """Relationship between users and their inventories.""" + __table_args__ = {'schema': 'common'} + user_id = db.Column(db.UUID(as_uuid=True), db.ForeignKey(User.id), primary_key=True) + inventory_id = db.Column(db.Unicode(), db.ForeignKey(Inventory.id), primary_key=True) diff --git a/requirements.txt b/requirements.txt index e49631ac..125d7f78 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,7 +25,7 @@ requests==2.19.1 requests-mock==1.5.2 SQLAlchemy==1.2.14 SQLAlchemy-Utils==0.33.6 -teal==0.2.0a32 +teal==0.2.0a33 webargs==4.0.0 Werkzeug==0.14.1 sqlalchemy-citext==1.3.post0 diff --git a/setup.py b/setup.py index 0e941894..cb1f1683 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ setup( long_description=long_description, long_description_content_type='text/markdown', install_requires=[ - 'teal>=0.2.0a32', # teal always first + 'teal>=0.2.0a33', # teal always first 'click', 'click-spinner', 'ereuse-utils[Naming]>=0.4b14',