예제 #1
0
class Path(db.Model):
    id = db.Column(db.UUID(as_uuid=True),
                   primary_key=True,
                   server_default=db.text('gen_random_uuid()'))
    lot_id = db.Column(db.UUID(as_uuid=True),
                       db.ForeignKey(Lot.id),
                       nullable=False)
    lot = db.relationship(Lot,
                          backref=db.backref('paths',
                                             lazy=True,
                                             collection_class=set,
                                             cascade=CASCADE_OWN),
                          primaryjoin=Lot.id == lot_id)
    path = db.Column(LtreeType, nullable=False)
    created = db.Column(db.TIMESTAMP(timezone=True),
                        server_default=db.text('CURRENT_TIMESTAMP'))
    created.comment = """
            When Devicehub created this.
        """

    __table_args__ = (
        # dag.delete_edge needs to disable internally/temporarily the unique constraint
        db.UniqueConstraint(path,
                            name='path_unique',
                            deferrable=True,
                            initially='immediate'),
        db.Index('path_gist', path, postgresql_using='gist'),
        db.Index('path_btree', path, postgresql_using='btree'),
        db.Index('lot_id_index', lot_id, postgresql_using='hash'))

    def __init__(self, lot: Lot) -> None:
        super().__init__(lot=lot)
        self.path = UUIDLtree(lot.id)

    @classmethod
    def add(cls, parent_id: uuid.UUID, child_id: uuid.UUID):
        """Creates an edge between parent and child."""
        db.session.execute(db.func.add_edge(str(parent_id), str(child_id)))

    @classmethod
    def delete(cls, parent_id: uuid.UUID, child_id: uuid.UUID):
        """Deletes the edge between parent and child."""
        db.session.execute(db.func.delete_edge(str(parent_id), str(child_id)))

    @classmethod
    def has_lot(cls, parent_id: uuid.UUID, child_id: uuid.UUID) -> bool:
        parent_id = UUIDLtree.convert(parent_id)
        child_id = UUIDLtree.convert(child_id)
        return bool(
            db.session.execute(
                "SELECT 1 from path where path ~ '*.{}.*.{}.*'".format(
                    parent_id, child_id)).first())
예제 #2
0
class User(Thing):
    __table_args__ = {'schema': 'common'}
    id = Column(UUID(as_uuid=True), default=uuid4, primary_key=True)
    email = Column(EmailType, nullable=False, unique=True)
    password = Column(
        PasswordType(max_length=STR_SIZE,
                     onload=lambda **kwargs: dict(
                         schemes=app.config['PASSWORD_SCHEMES'], **kwargs)))
    token = Column(UUID(as_uuid=True),
                   default=uuid4,
                   unique=True,
                   nullable=False)
    inventories = db.relationship(Inventory,
                                  backref=db.backref('users',
                                                     lazy=True,
                                                     collection_class=set),
                                  secondary=lambda: UserInventory.__table__,
                                  collection_class=set)

    # todo set restriction that user has, at least, one active db

    def __init__(self, email, password=None, inventories=None) -> None:
        """
        Creates an user.
        :param email:
        :param password:
        :param inventories: A set of Inventory where the user has
        access to. If none, the user is granted access to the current
        inventory.
        """
        inventories = inventories or {Inventory.current}
        super().__init__(email=email,
                         password=password,
                         inventories=inventories)

    def __repr__(self) -> str:
        return '<User {0.email}>'.format(self)

    @property
    def type(self) -> str:
        return self.__class__.__name__

    @property
    def individual(self):
        """The individual associated for this database, or None."""
        return next(iter(self.individuals), None)
예제 #3
0
class Session(Thing):
    __table_args__ = {'schema': 'common'}
    id = Column(BigInteger, Sequence('device_seq'), primary_key=True)
    expired = Column(BigInteger, default=0)
    token = Column(UUID(as_uuid=True),
                   default=uuid4,
                   unique=True,
                   nullable=False)
    type = Column(IntEnum(SessionType),
                  default=SessionType.Internal,
                  nullable=False)
    user_id = db.Column(db.UUID(as_uuid=True), db.ForeignKey(User.id))
    user = db.relationship(User,
                           backref=db.backref('sessions',
                                              lazy=True,
                                              collection_class=set),
                           collection_class=set)

    def __str__(self) -> str:
        return '{0.token}'.format(self)
예제 #4
0
class Lot(Thing):
    id = db.Column(UUID(as_uuid=True),
                   primary_key=True)  # uuid is generated on init by default
    name = db.Column(CIText(), nullable=False)
    description = db.Column(CIText())
    description.comment = """A comment about the lot."""
    closed = db.Column(db.Boolean, default=False, nullable=False)
    closed.comment = """
            A closed lot cannot be modified anymore.
        """
    devices = db.relationship(Device,
                              backref=db.backref('lots',
                                                 lazy=True,
                                                 collection_class=set),
                              secondary=lambda: LotDevice.__table__,
                              lazy=True,
                              collection_class=set)
    """
    The **children** devices that the lot has.
    
    Note that the lot can have more devices, if they are inside 
    descendant lots.
    """
    parents = db.relationship(
        lambda: Lot,
        viewonly=True,
        lazy=True,
        collection_class=set,
        secondary=lambda: LotParent.__table__,
        primaryjoin=lambda: Lot.id == LotParent.child_id,
        secondaryjoin=lambda: LotParent.parent_id == Lot.id,
        cascade='refresh-expire',  # propagate changes outside ORM
        backref=db.backref('children',
                           viewonly=True,
                           lazy=True,
                           cascade='refresh-expire',
                           collection_class=set))
    """The parent lots."""

    all_devices = db.relationship(
        Device,
        viewonly=True,
        lazy=True,
        collection_class=set,
        secondary=lambda: LotDeviceDescendants.__table__,
        primaryjoin=lambda: Lot.id == LotDeviceDescendants.ancestor_lot_id,
        secondaryjoin=lambda: LotDeviceDescendants.device_id == Device.id)
    """All devices, including components, inside this lot and its
    descendants.
    """
    def __init__(self,
                 name: str,
                 closed: bool = closed.default.arg,
                 description: str = None) -> None:
        """
        Initializes a lot
        :param name:
        :param closed:
        """
        super().__init__(id=uuid.uuid4(),
                         name=name,
                         closed=closed,
                         description=description)
        Path(self)  # Lots have always one edge per default.

    @property
    def type(self) -> str:
        return self.__class__.__name__

    @property
    def url(self) -> urlutils.URL:
        """The URL where to GET this event."""
        return urlutils.URL(url_for_resource(Lot, item_id=self.id))

    @property
    def descendants(self):
        return self.descendantsq(self.id)

    @classmethod
    def descendantsq(cls, id):
        _id = UUIDLtree.convert(id)
        return (cls.id == Path.lot_id) & Path.path.lquery(
            exp.cast('*.{}.*'.format(_id), LQUERY))

    @classmethod
    def roots(cls):
        """Gets the lots that are not under any other lot."""
        return cls.query.join(cls.paths).filter(db.func.nlevel(Path.path) == 1)

    def add_children(self, *children):
        """Add children lots to this lot.

        This operation is highly costly as it forces refreshing
        many models in session.
        """
        for child in children:
            if isinstance(child, Lot):
                Path.add(self.id, child.id)
                db.session.refresh(child)
            else:
                assert isinstance(child, uuid.UUID)
                Path.add(self.id, child)
        # We need to refresh the models involved in this operation
        # outside the session / ORM control so the models
        # that have relationships to this model
        # with the cascade 'refresh-expire' can welcome the changes
        db.session.refresh(self)

    def remove_children(self, *children):
        """Remove children lots from this lot.

        This operation is highly costly as it forces refreshing
        many models in session.
        """
        for child in children:
            if isinstance(child, Lot):
                Path.delete(self.id, child.id)
                db.session.refresh(child)
            else:
                assert isinstance(child, uuid.UUID)
                Path.delete(self.id, child)
        db.session.refresh(self)

    def delete(self):
        """Deletes the lot.

        This method removes the children lots and children
        devices orphan from this lot and then marks this lot
        for deletion.
        """
        self.remove_children(*self.children)
        db.session.delete(self)

    def _refresh_models_with_relationships_to_lots(self):
        session = db.Session.object_session(self)
        for model in session:
            if isinstance(model, (Device, Lot, Path)):
                session.expire(model)

    def __contains__(self, child: Union['Lot', Device]):
        if isinstance(child, Lot):
            return Path.has_lot(self.id, child.id)
        elif isinstance(child, Device):
            device = db.session.query(LotDeviceDescendants) \
                .filter(LotDeviceDescendants.device_id == child.id) \
                .filter(LotDeviceDescendants.ancestor_lot_id == self.id) \
                .one_or_none()
            return device
        else:
            raise TypeError(
                'Lot only contains devices and lots, not {}'.format(
                    child.__class__))

    def __repr__(self) -> str:
        return '<Lot {0.name} devices={0.devices!r}>'.format(self)
예제 #5
0
class Deliverynote(Thing):
    id = db.Column(UUID(as_uuid=True),
                   primary_key=True)  # uuid is generated on init by default
    document_id = db.Column(CIText(), nullable=False)
    creator_id = db.Column(UUID(as_uuid=True),
                           db.ForeignKey(User.id),
                           nullable=False,
                           default=lambda: g.user.id)
    creator = db.relationship(User, primaryjoin=creator_id == User.id)
    supplier_email = db.Column(CIText(),
                               db.ForeignKey(User.email),
                               nullable=False,
                               default=lambda: g.user.email)
    supplier = db.relationship(
        User, primaryjoin=lambda: Deliverynote.supplier_email == User.email)
    receiver_address = db.Column(CIText(),
                                 db.ForeignKey(User.email),
                                 nullable=False,
                                 default=lambda: g.user.email)
    receiver = db.relationship(
        User, primaryjoin=lambda: Deliverynote.receiver_address == User.email)
    date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    date.comment = 'The date the DeliveryNote initiated'
    amount = db.Column(db.Integer,
                       check_range('amount', min=0, max=100),
                       default=0)
    # The following fields are supposed to be 0:N relationships
    # to SnapshotDelivery entity.
    # At this stage of implementation they will treated as a
    # comma-separated string of the devices expexted/transfered
    expected_devices = db.Column(JSONB, nullable=False)
    # expected_devices = db.Column(db.ARRAY(JSONB, dimensions=1), nullable=False)
    transferred_devices = db.Column(db.ARRAY(db.Integer, dimensions=1),
                                    nullable=True)
    transfer_state = db.Column(IntEnum(TransferState),
                               default=TransferState.Initial,
                               nullable=False)
    transfer_state.comment = TransferState.__doc__
    lot_id = db.Column(UUID(as_uuid=True),
                       db.ForeignKey(Lot.id),
                       nullable=False)
    lot = db.relationship(Lot,
                          backref=db.backref('deliverynote',
                                             uselist=False,
                                             lazy=True),
                          lazy=True,
                          primaryjoin=Lot.id == lot_id)

    def __init__(self, document_id: str, amount: str, date,
                 supplier_email: str, expected_devices: Iterable,
                 transfer_state: TransferState) -> None:
        """Initializes a delivery note
        """
        super().__init__(id=uuid.uuid4(),
                         document_id=document_id,
                         amount=amount,
                         date=date,
                         supplier_email=supplier_email,
                         expected_devices=expected_devices,
                         transfer_state=transfer_state)

    @property
    def type(self) -> str:
        return self.__class__.__name__

    @property
    def url(self) -> urlutils.URL:
        """The URL where to GET this action."""
        return urlutils.URL(url_for_resource(Deliverynote, item_id=self.id))

    def delete(self):
        """Deletes the deliverynote.

        This method removes the delivery note.
        """
        db.session.delete(self)

    def __repr__(self) -> str:
        return '<Deliverynote {0.document_id}>'.format(self)