Enhance Lot; use timezone aware datetime
This commit is contained in:
parent
f5d69070e6
commit
2ed558ac2b
|
@ -67,13 +67,13 @@ class Event(Thing):
|
|||
description.comment = """
|
||||
A comment about the event.
|
||||
"""
|
||||
start_time = Column(DateTime)
|
||||
start_time = Column(db.TIMESTAMP(timezone=True))
|
||||
start_time.comment = """
|
||||
When the action starts. For some actions like reservations
|
||||
the time when they are available, for others like renting
|
||||
when the renting starts.
|
||||
"""
|
||||
end_time = Column(DateTime)
|
||||
end_time = Column(db.TIMESTAMP(timezone=True))
|
||||
end_time.comment = """
|
||||
When the action ends. For some actions like reservations
|
||||
the time when they expire, for others like renting
|
||||
|
|
|
@ -12,12 +12,11 @@ from ereuse_devicehub.db import db
|
|||
from ereuse_devicehub.resources.device.models import Device
|
||||
from ereuse_devicehub.resources.models import STR_SIZE, Thing
|
||||
from ereuse_devicehub.resources.user.models import User
|
||||
from teal.db import UUIDLtree
|
||||
|
||||
|
||||
class Lot(Thing):
|
||||
id = db.Column(UUID(as_uuid=True),
|
||||
primary_key=True,
|
||||
server_default=db.text('gen_random_uuid()'))
|
||||
id = db.Column(UUID(as_uuid=True), primary_key=True) # uuid is generated on init by default
|
||||
name = db.Column(db.Unicode(STR_SIZE), nullable=False)
|
||||
closed = db.Column(db.Boolean, default=False, nullable=False)
|
||||
closed.comment = """
|
||||
|
@ -28,8 +27,14 @@ class Lot(Thing):
|
|||
secondary=lambda: LotDevice.__table__,
|
||||
collection_class=set)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return '<Lot {0.name} devices={0.devices!r}>'.format(self)
|
||||
def __init__(self, name: str, closed: bool = closed.default.arg) -> None:
|
||||
"""
|
||||
Initializes a lot
|
||||
:param name:
|
||||
:param closed:
|
||||
"""
|
||||
super().__init__(id=uuid.uuid4(), name=name, closed=closed)
|
||||
Edge(self) # Lots have always one edge per default.
|
||||
|
||||
def add_child(self, child: 'Lot'):
|
||||
"""Adds a child to this lot."""
|
||||
|
@ -43,6 +48,9 @@ class Lot(Thing):
|
|||
def __contains__(self, child: 'Lot'):
|
||||
return Edge.has_lot(self.id, child.id)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return '<Lot {0.name} devices={0.devices!r}>'.format(self)
|
||||
|
||||
|
||||
class LotDevice(db.Model):
|
||||
device_id = db.Column(db.BigInteger, db.ForeignKey(Device.id), primary_key=True)
|
||||
|
@ -58,7 +66,7 @@ class LotDevice(db.Model):
|
|||
"""
|
||||
|
||||
|
||||
class Edge(Thing):
|
||||
class Edge(db.Model):
|
||||
id = db.Column(db.UUID(as_uuid=True),
|
||||
primary_key=True,
|
||||
server_default=db.text('gen_random_uuid()'))
|
||||
|
@ -66,7 +74,11 @@ class Edge(Thing):
|
|||
lot = db.relationship(Lot,
|
||||
backref=db.backref('edges', lazy=True, collection_class=set),
|
||||
primaryjoin=Lot.id == lot_id)
|
||||
path = db.Column(LtreeType, unique=True, nullable=False)
|
||||
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__ = (
|
||||
db.UniqueConstraint(path, name='edge_path_unique', deferrable=True, initially='immediate'),
|
||||
|
@ -74,8 +86,13 @@ class Edge(Thing):
|
|||
db.Index('path_btree', path, postgresql_using='btree')
|
||||
)
|
||||
|
||||
def __init__(self, lot: Lot) -> None:
|
||||
super().__init__(lot=lot)
|
||||
self.path = UUIDLtree(lot.id)
|
||||
|
||||
def children(self) -> Set['Edge']:
|
||||
"""Get the children edges."""
|
||||
# todo is it useful? test it when first usage
|
||||
# From https://stackoverflow.com/a/41158890
|
||||
exp = '*.{}.*{{1}}'.format(self.lot_id)
|
||||
return set(self.query
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from ereuse_devicehub.db import db
|
||||
|
||||
|
@ -10,11 +10,16 @@ STR_XSM_SIZE = 16
|
|||
|
||||
class Thing(db.Model):
|
||||
__abstract__ = True
|
||||
updated = db.Column(db.DateTime, onupdate=datetime.utcnow)
|
||||
# todo make updated to auto-update
|
||||
updated = db.Column(db.TIMESTAMP(timezone=True),
|
||||
nullable=False,
|
||||
server_default=db.text('CURRENT_TIMESTAMP'))
|
||||
updated.comment = """
|
||||
When this was last changed.
|
||||
"""
|
||||
created = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
created = db.Column(db.TIMESTAMP(timezone=True),
|
||||
nullable=False,
|
||||
server_default=db.text('CURRENT_TIMESTAMP'))
|
||||
created.comment = """
|
||||
When Devicehub created this.
|
||||
"""
|
||||
|
@ -22,4 +27,4 @@ class Thing(db.Model):
|
|||
def __init__(self, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
if not self.created:
|
||||
self.created = datetime.utcnow()
|
||||
self.created = datetime.now(timezone.utc)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import ipaddress
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import pytest
|
||||
from flask import current_app as app, g
|
||||
|
@ -38,8 +38,8 @@ def test_erase_basic():
|
|||
erasure = models.EraseBasic(
|
||||
device=HardDrive(serial_number='foo', manufacturer='bar', model='foo-bar'),
|
||||
zeros=True,
|
||||
start_time=datetime.now(),
|
||||
end_time=datetime.now(),
|
||||
start_time=datetime.now(timezone.utc),
|
||||
end_time=datetime.now(timezone.utc),
|
||||
error=False
|
||||
)
|
||||
db.session.add(erasure)
|
||||
|
@ -59,8 +59,8 @@ def test_validate_device_data_storage():
|
|||
models.EraseBasic(
|
||||
device=GraphicCard(serial_number='foo', manufacturer='bar', model='foo-bar'),
|
||||
clean_with_zeros=True,
|
||||
start_time=datetime.now(),
|
||||
end_time=datetime.now(),
|
||||
start_time=datetime.now(timezone.utc),
|
||||
end_time=datetime.now(timezone.utc),
|
||||
error=False
|
||||
)
|
||||
|
||||
|
@ -70,19 +70,19 @@ def test_erase_sectors_steps():
|
|||
erasure = models.EraseSectors(
|
||||
device=SolidStateDrive(serial_number='foo', manufacturer='bar', model='foo-bar'),
|
||||
zeros=True,
|
||||
start_time=datetime.now(),
|
||||
end_time=datetime.now(),
|
||||
start_time=datetime.now(timezone.utc),
|
||||
end_time=datetime.now(timezone.utc),
|
||||
error=False,
|
||||
steps=[
|
||||
models.StepZero(error=False,
|
||||
start_time=datetime.now(),
|
||||
end_time=datetime.now()),
|
||||
start_time=datetime.now(timezone.utc),
|
||||
end_time=datetime.now(timezone.utc)),
|
||||
models.StepRandom(error=False,
|
||||
start_time=datetime.now(),
|
||||
end_time=datetime.now()),
|
||||
start_time=datetime.now(timezone.utc),
|
||||
end_time=datetime.now(timezone.utc)),
|
||||
models.StepZero(error=False,
|
||||
start_time=datetime.now(),
|
||||
end_time=datetime.now())
|
||||
start_time=datetime.now(timezone.utc),
|
||||
end_time=datetime.now(timezone.utc))
|
||||
]
|
||||
)
|
||||
db.session.add(erasure)
|
||||
|
|
|
@ -1,13 +1,25 @@
|
|||
import pytest
|
||||
from flask import g
|
||||
from sqlalchemy_utils import Ltree
|
||||
|
||||
from ereuse_devicehub.db import db
|
||||
from ereuse_devicehub.resources.device.models import Desktop
|
||||
from ereuse_devicehub.resources.enums import ComputerChassis
|
||||
from ereuse_devicehub.resources.lot.models import Edge, Lot, LotDevice
|
||||
from ereuse_devicehub.resources.lot.models import Lot, LotDevice
|
||||
from tests import conftest
|
||||
|
||||
"""
|
||||
In case of error, debug with:
|
||||
|
||||
try:
|
||||
with db.session.begin_nested():
|
||||
|
||||
except Exception as e:
|
||||
db.session.commit()
|
||||
print(e)
|
||||
a=1
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
|
||||
def test_lot_device_relationship():
|
||||
|
@ -15,7 +27,7 @@ def test_lot_device_relationship():
|
|||
model='bar',
|
||||
manufacturer='foobar',
|
||||
chassis=ComputerChassis.Lunchbox)
|
||||
lot = Lot(name='lot1')
|
||||
lot = Lot('lot1')
|
||||
lot.devices.add(device)
|
||||
db.session.add(lot)
|
||||
db.session.flush()
|
||||
|
@ -30,15 +42,12 @@ def test_lot_device_relationship():
|
|||
|
||||
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
|
||||
def test_add_edge():
|
||||
child = Lot(name='child')
|
||||
parent = Lot(name='parent')
|
||||
"""Tests creating an edge between child - parent - grandparent."""
|
||||
child = Lot('child')
|
||||
parent = Lot('parent')
|
||||
db.session.add(child)
|
||||
db.session.add(parent)
|
||||
db.session.flush()
|
||||
# todo edges should automatically be created when the lot is created
|
||||
child.edges.add(Edge(path=Ltree(str(child.id).replace('-', '_'))))
|
||||
parent.edges.add(Edge(path=Ltree(str(parent.id).replace('-', '_'))))
|
||||
db.session.flush()
|
||||
|
||||
parent.add_child(child)
|
||||
|
||||
|
@ -51,11 +60,9 @@ def test_add_edge():
|
|||
assert len(child.edges) == 1
|
||||
assert len(parent.edges) == 1
|
||||
|
||||
grandparent = Lot(name='grandparent')
|
||||
grandparent = Lot('grandparent')
|
||||
db.session.add(grandparent)
|
||||
db.session.flush()
|
||||
grandparent.edges.add(Edge(path=Ltree(str(grandparent.id).replace('-', '_'))))
|
||||
db.session.flush()
|
||||
|
||||
grandparent.add_child(parent)
|
||||
parent.add_child(child)
|
||||
|
@ -63,3 +70,101 @@ def test_add_edge():
|
|||
assert parent in grandparent
|
||||
assert child in parent
|
||||
assert child in grandparent
|
||||
|
||||
|
||||
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
|
||||
def test_lot_multiple_parents():
|
||||
"""Tests creating a lot with two parent lots:
|
||||
|
||||
grandparent1 grandparent2
|
||||
\ /
|
||||
parent
|
||||
|
|
||||
child
|
||||
"""
|
||||
lots = Lot('child'), Lot('parent'), Lot('grandparent1'), Lot('grandparent2')
|
||||
child, parent, grandparent1, grandparent2 = lots
|
||||
db.session.add_all(lots)
|
||||
db.session.flush()
|
||||
|
||||
grandparent1.add_child(parent)
|
||||
assert parent in grandparent1
|
||||
parent.add_child(child)
|
||||
assert child in parent
|
||||
assert child in grandparent1
|
||||
grandparent2.add_child(parent)
|
||||
assert parent in grandparent1
|
||||
assert parent in grandparent2
|
||||
assert child in parent
|
||||
assert child in grandparent1
|
||||
assert child in grandparent2
|
||||
|
||||
grandparent1.remove_child(parent)
|
||||
assert parent not in grandparent1
|
||||
assert child in parent
|
||||
assert parent in grandparent2
|
||||
assert child not in grandparent1
|
||||
assert child in grandparent2
|
||||
|
||||
grandparent2.remove_child(parent)
|
||||
assert parent not in grandparent2
|
||||
assert parent not in grandparent1
|
||||
assert child not in grandparent2
|
||||
assert child not in grandparent1
|
||||
assert child in parent
|
||||
|
||||
parent.remove_child(child)
|
||||
assert child not in parent
|
||||
assert len(child.edges) == 1
|
||||
assert len(parent.edges) == 1
|
||||
|
||||
|
||||
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
|
||||
def test_lot_unite_graphs():
|
||||
"""Adds and removes children uniting already existing graphs.
|
||||
|
||||
1 3
|
||||
\/
|
||||
2
|
||||
|
||||
4
|
||||
| \
|
||||
| 6
|
||||
\ /
|
||||
5
|
||||
| \
|
||||
7 8
|
||||
|
||||
This builds the graph and then unites 2 - 4.
|
||||
"""
|
||||
|
||||
lots = tuple(Lot(str(i)) for i in range(1, 9))
|
||||
l1, l2, l3, l4, l5, l6, l7, l8 = lots
|
||||
db.session.add_all(lots)
|
||||
db.session.flush()
|
||||
|
||||
l1.add_child(l2)
|
||||
assert l2 in l1
|
||||
l3.add_child(l2)
|
||||
assert l2 in l3
|
||||
l5.add_child(l7)
|
||||
assert l7 in l5
|
||||
l4.add_child(l5)
|
||||
assert l5 in l4
|
||||
assert l7 in l4
|
||||
l5.add_child(l8)
|
||||
assert l8 in l5
|
||||
l4.add_child(l6)
|
||||
assert l6 in l4
|
||||
l6.add_child(l5)
|
||||
assert l5 in l6 and l5 in l4
|
||||
|
||||
# We unite the two graphs
|
||||
l2.add_child(l4)
|
||||
assert l4 in l2 and l5 in l2 and l6 in l2 and l7 in l2 and l8 in l2
|
||||
assert l4 in l3 and l5 in l3 and l6 in l3 and l7 in l3 and l8 in l3
|
||||
|
||||
# We remove the union
|
||||
l2.remove_child(l4)
|
||||
assert l4 not in l2 and l5 not in l2 and l6 not in l2 and l7 not in l2 and l8 not in l2
|
||||
assert l4 not in l3 and l5 not in l3 and l6 not in l3 and l7 not in l3 and l8 not in l3
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from distutils.version import StrictVersion
|
||||
from typing import List, Tuple
|
||||
from uuid import uuid4
|
||||
|
@ -29,7 +29,7 @@ def test_snapshot_model():
|
|||
device = m.Desktop(serial_number='a1', chassis=ComputerChassis.Tower)
|
||||
# noinspection PyArgumentList
|
||||
snapshot = Snapshot(uuid=uuid4(),
|
||||
end_time=datetime.now(),
|
||||
end_time=datetime.now(timezone.utc),
|
||||
version='1.0',
|
||||
software=SnapshotSoftware.DesktopApp,
|
||||
elapsed=timedelta(seconds=25))
|
||||
|
|
Reference in a new issue