2018-08-03 16:15:08 +00:00
|
|
|
from itertools import chain
|
|
|
|
from operator import attrgetter
|
|
|
|
from uuid import uuid4
|
|
|
|
|
|
|
|
from flask import current_app as app, g
|
|
|
|
from sqlalchemy import Column, Enum as DBEnum, ForeignKey, Unicode, UniqueConstraint
|
|
|
|
from sqlalchemy.dialects.postgresql import UUID
|
|
|
|
from sqlalchemy.ext.declarative import declared_attr
|
2018-09-20 07:28:52 +00:00
|
|
|
from sqlalchemy.orm import backref, relationship, validates
|
2018-08-03 16:15:08 +00:00
|
|
|
from sqlalchemy_utils import EmailType, PhoneNumberType
|
|
|
|
from teal import enums
|
2018-09-29 17:39:38 +00:00
|
|
|
from teal.db import DBError, INHERIT_COND, POLYMORPHIC_ID, POLYMORPHIC_ON
|
2018-09-20 07:28:52 +00:00
|
|
|
from teal.marshmallow import ValidationError
|
2018-09-29 17:39:38 +00:00
|
|
|
from werkzeug.exceptions import NotImplemented, UnprocessableEntity
|
2018-08-03 16:15:08 +00:00
|
|
|
|
2018-09-07 10:38:02 +00:00
|
|
|
from ereuse_devicehub.resources.models import STR_SIZE, STR_SM_SIZE, Thing
|
|
|
|
from ereuse_devicehub.resources.user.models import User
|
|
|
|
|
2018-08-03 16:15:08 +00:00
|
|
|
|
|
|
|
class JoinedTableMixin:
|
|
|
|
# noinspection PyMethodParameters
|
|
|
|
@declared_attr
|
|
|
|
def id(cls):
|
|
|
|
return Column(UUID(as_uuid=True), ForeignKey(Agent.id), primary_key=True)
|
|
|
|
|
|
|
|
|
|
|
|
class Agent(Thing):
|
|
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
|
|
|
|
type = Column(Unicode, nullable=False)
|
|
|
|
name = Column(Unicode(length=STR_SM_SIZE))
|
|
|
|
name.comment = """
|
|
|
|
The name of the organization or person.
|
|
|
|
"""
|
|
|
|
tax_id = Column(Unicode(length=STR_SM_SIZE))
|
|
|
|
tax_id.comment = """
|
|
|
|
The Tax / Fiscal ID of the organization,
|
|
|
|
e.g. the TIN in the US or the CIF/NIF in Spain.
|
|
|
|
"""
|
|
|
|
country = Column(DBEnum(enums.Country))
|
|
|
|
country.comment = """
|
|
|
|
Country issuing the tax_id number.
|
|
|
|
"""
|
|
|
|
telephone = Column(PhoneNumberType())
|
|
|
|
email = Column(EmailType, unique=True)
|
|
|
|
|
|
|
|
__table_args__ = (
|
|
|
|
UniqueConstraint(tax_id, country, name='Registration Number per country.'),
|
|
|
|
)
|
|
|
|
|
|
|
|
@declared_attr
|
|
|
|
def __mapper_args__(cls):
|
|
|
|
"""
|
|
|
|
Defines inheritance.
|
|
|
|
|
|
|
|
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
|
|
|
|
extensions/declarative/api.html
|
|
|
|
#sqlalchemy.ext.declarative.declared_attr>`_
|
|
|
|
"""
|
|
|
|
args = {POLYMORPHIC_ID: cls.t}
|
|
|
|
if cls.t == 'Agent':
|
|
|
|
args[POLYMORPHIC_ON] = cls.type
|
|
|
|
if JoinedTableMixin in cls.mro():
|
|
|
|
args[INHERIT_COND] = cls.id == Agent.id
|
|
|
|
return args
|
|
|
|
|
|
|
|
@property
|
|
|
|
def events(self) -> list:
|
|
|
|
# todo test
|
|
|
|
return sorted(chain(self.events_agent, self.events_to), key=attrgetter('created'))
|
|
|
|
|
2018-09-20 07:28:52 +00:00
|
|
|
@validates('name')
|
|
|
|
def does_not_contain_slash(self, _, value: str):
|
|
|
|
if '/' in value:
|
|
|
|
raise ValidationError('Name cannot contain slash \'')
|
|
|
|
return value
|
|
|
|
|
2018-08-03 16:15:08 +00:00
|
|
|
def __repr__(self) -> str:
|
|
|
|
return '<{0.t} {0.name}>'.format(self)
|
|
|
|
|
|
|
|
|
|
|
|
class Organization(JoinedTableMixin, Agent):
|
|
|
|
def __init__(self, name: str, **kwargs) -> None:
|
|
|
|
super().__init__(**kwargs, name=name)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_default_org_id(cls) -> UUID:
|
|
|
|
"""Retrieves the default organization."""
|
2018-09-29 17:39:38 +00:00
|
|
|
try:
|
|
|
|
return g.setdefault('org_id',
|
|
|
|
Organization.query.filter_by(
|
|
|
|
**app.config.get_namespace('ORGANIZATION_')
|
|
|
|
).one().id)
|
|
|
|
except (DBError, UnprocessableEntity):
|
|
|
|
# todo test how well this works
|
|
|
|
raise NotImplemented('Error in getting the default organization. '
|
|
|
|
'Is the DB initialized?')
|
2018-08-03 16:15:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Individual(JoinedTableMixin, Agent):
|
|
|
|
active_org_id = Column(UUID(as_uuid=True), ForeignKey(Organization.id))
|
|
|
|
active_org = relationship(Organization, primaryjoin=active_org_id == Organization.id)
|
|
|
|
|
2018-09-20 14:40:41 +00:00
|
|
|
user_id = Column(UUID(as_uuid=True), ForeignKey(User.id), unique=True)
|
|
|
|
user = relationship(User,
|
|
|
|
backref=backref('individuals', lazy=True, collection_class=set),
|
|
|
|
primaryjoin=user_id == User.id)
|
|
|
|
|
2018-08-03 16:15:08 +00:00
|
|
|
|
|
|
|
class Membership(Thing):
|
|
|
|
"""Organizations that are related to the Individual.
|
|
|
|
|
|
|
|
For example, because the individual works in or because is a member of.
|
|
|
|
"""
|
|
|
|
id = Column(Unicode(length=STR_SIZE))
|
|
|
|
organization_id = Column(UUID(as_uuid=True), ForeignKey(Organization.id), primary_key=True)
|
|
|
|
organization = relationship(Organization,
|
|
|
|
backref=backref('members', collection_class=set, lazy=True),
|
|
|
|
primaryjoin=organization_id == Organization.id)
|
|
|
|
individual_id = Column(UUID(as_uuid=True), ForeignKey(Individual.id), primary_key=True)
|
|
|
|
individual = relationship(Individual,
|
|
|
|
backref=backref('member_of', collection_class=set, lazy=True),
|
|
|
|
primaryjoin=individual_id == Individual.id)
|
|
|
|
|
|
|
|
def __init__(self, organization: Organization, individual: Individual, id: str = None) -> None:
|
|
|
|
super().__init__(organization=organization,
|
|
|
|
individual=individual,
|
|
|
|
id=id)
|
|
|
|
|
|
|
|
__table_args__ = (
|
|
|
|
UniqueConstraint(id, organization_id, name='One member id per organization.'),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class Person(Individual):
|
|
|
|
"""
|
|
|
|
A person in the system. There can be several persons pointing to
|
|
|
|
a real.
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class System(Individual):
|
|
|
|
pass
|