Timestamps and uuids everywhere
This commit is contained in:
parent
4fc1f9f2d4
commit
5f54468082
@ -1,8 +1,8 @@
|
||||
"""initial
|
||||
|
||||
Revision ID: 74ac90c019bf
|
||||
Revision ID: 359caaf8c5f1
|
||||
Revises:
|
||||
Create Date: 2018-09-18 21:00:05.497858
|
||||
Create Date: 2018-09-20 10:25:44.438679
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
@ -10,7 +10,7 @@ import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '74ac90c019bf'
|
||||
revision = '359caaf8c5f1'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
@ -22,18 +22,22 @@ def upgrade():
|
||||
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('attachments',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('time_created', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('time_updated', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('uuid_generate_v4()'), nullable=False),
|
||||
sa.Column('filename', sa.String(), nullable=True),
|
||||
sa.Column('object_name', sa.String(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('object_name')
|
||||
)
|
||||
op.create_table('pe_number',
|
||||
op.create_table('pe_numbers',
|
||||
sa.Column('number', sa.String(), nullable=False),
|
||||
sa.Column('description', sa.String(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('number')
|
||||
)
|
||||
op.create_table('roles',
|
||||
sa.Column('time_created', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('time_updated', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('uuid_generate_v4()'), nullable=False),
|
||||
sa.Column('name', sa.String(), nullable=True),
|
||||
sa.Column('description', sa.String(), nullable=True),
|
||||
@ -42,8 +46,10 @@ def upgrade():
|
||||
)
|
||||
op.create_index(op.f('ix_roles_name'), 'roles', ['name'], unique=True)
|
||||
op.create_index(op.f('ix_roles_permissions'), 'roles', ['permissions'], unique=False)
|
||||
op.create_table('task_order',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
op.create_table('task_orders',
|
||||
sa.Column('time_created', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('time_updated', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('uuid_generate_v4()'), nullable=False),
|
||||
sa.Column('number', sa.String(), nullable=True),
|
||||
sa.Column('source', sa.Enum('MANUAL', 'EDA', name='source', native_enum=False), nullable=True),
|
||||
sa.Column('funding_type', sa.Enum('RDTE', 'OM', 'PROC', 'OTHER', name='fundingtype', native_enum=False), nullable=True),
|
||||
@ -55,12 +61,14 @@ def upgrade():
|
||||
sa.Column('clin_2001', sa.Integer(), nullable=True),
|
||||
sa.Column('clin_2003', sa.Integer(), nullable=True),
|
||||
sa.Column('expiration_date', sa.Date(), nullable=True),
|
||||
sa.Column('attachment_id', sa.Integer(), nullable=True),
|
||||
sa.Column('attachment_id', postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.ForeignKeyConstraint(['attachment_id'], ['attachments.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('number')
|
||||
)
|
||||
op.create_table('users',
|
||||
sa.Column('time_created', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('time_updated', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('uuid_generate_v4()'), nullable=False),
|
||||
sa.Column('username', sa.String(), nullable=True),
|
||||
sa.Column('atat_role_id', postgresql.UUID(as_uuid=True), nullable=True),
|
||||
@ -74,6 +82,8 @@ def upgrade():
|
||||
sa.UniqueConstraint('email')
|
||||
)
|
||||
op.create_table('request_reviews',
|
||||
sa.Column('time_created', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('time_updated', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('uuid_generate_v4()'), nullable=False),
|
||||
sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('comment', sa.String(), nullable=True),
|
||||
@ -87,15 +97,18 @@ def upgrade():
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('requests',
|
||||
sa.Column('time_updated', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('uuid_generate_v4()'), nullable=False),
|
||||
sa.Column('time_created', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
|
||||
sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('task_order_id', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['task_order_id'], ['task_order.id'], ),
|
||||
sa.Column('task_order_id', postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.ForeignKeyConstraint(['task_order_id'], ['task_orders.id'], ),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('request_internal_comments',
|
||||
sa.Column('time_created', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('time_updated', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('uuid_generate_v4()'), nullable=False),
|
||||
sa.Column('text', sa.String(), nullable=True),
|
||||
sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
@ -108,7 +121,6 @@ def upgrade():
|
||||
sa.Column('time_created', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('time_updated', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('uuid_generate_v4()'), nullable=False),
|
||||
sa.Column('name', sa.String(), nullable=True),
|
||||
sa.Column('request_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('sequence', sa.BigInteger(), nullable=False),
|
||||
sa.Column('am_poc', sa.Boolean(), nullable=True),
|
||||
@ -133,6 +145,7 @@ def upgrade():
|
||||
sa.Column('average_daily_traffic_gb', sa.Integer(), nullable=True),
|
||||
sa.Column('rationalization_software_systems', sa.String(), nullable=True),
|
||||
sa.Column('organization_providing_assistance', sa.String(), nullable=True),
|
||||
sa.Column('name', sa.String(), nullable=True),
|
||||
sa.Column('citizenship', sa.String(), nullable=True),
|
||||
sa.Column('designation', sa.String(), nullable=True),
|
||||
sa.Column('phone_number', sa.String(), nullable=True),
|
||||
@ -178,19 +191,22 @@ def upgrade():
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('request_status_events',
|
||||
sa.Column('time_updated', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('uuid_generate_v4()'), nullable=False),
|
||||
sa.Column('new_status', sa.Enum('STARTED', 'SUBMITTED', 'PENDING_FINANCIAL_VERIFICATION', 'PENDING_CCPO_ACCEPTANCE', 'PENDING_CCPO_APPROVAL', 'CHANGES_REQUESTED', 'CHANGES_REQUESTED_TO_FINVER', 'APPROVED', 'EXPIRED', 'DELETED', name='requeststatus', native_enum=False), nullable=True),
|
||||
sa.Column('time_created', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
|
||||
sa.Column('request_id', postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.Column('sequence', sa.BigInteger(), nullable=False),
|
||||
sa.Column('request_revision_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('request_review_id', postgresql.UUID(as_uuid=True), server_default=sa.text('uuid_generate_v4()'), nullable=True),
|
||||
sa.Column('request_review_id', postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.ForeignKeyConstraint(['request_id'], ['requests.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['request_review_id'], ['request_reviews.id'], ),
|
||||
sa.ForeignKeyConstraint(['request_revision_id'], ['request_revisions.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('workspace_role',
|
||||
op.create_table('workspace_roles',
|
||||
sa.Column('time_created', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('time_updated', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('uuid_generate_v4()'), nullable=False),
|
||||
sa.Column('workspace_id', postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.Column('role_id', postgresql.UUID(as_uuid=True), nullable=True),
|
||||
@ -200,9 +216,9 @@ def upgrade():
|
||||
sa.ForeignKeyConstraint(['workspace_id'], ['workspaces.id'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_workspace_role_user_id'), 'workspace_role', ['user_id'], unique=False)
|
||||
op.create_index(op.f('ix_workspace_role_workspace_id'), 'workspace_role', ['workspace_id'], unique=False)
|
||||
op.create_index('workspace_role_user_workspace', 'workspace_role', ['user_id', 'workspace_id'], unique=True)
|
||||
op.create_index(op.f('ix_workspace_roles_user_id'), 'workspace_roles', ['user_id'], unique=False)
|
||||
op.create_index(op.f('ix_workspace_roles_workspace_id'), 'workspace_roles', ['workspace_id'], unique=False)
|
||||
op.create_index('workspace_role_user_workspace', 'workspace_roles', ['user_id', 'workspace_id'], unique=True)
|
||||
op.create_table('environments',
|
||||
sa.Column('time_created', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('time_updated', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
@ -213,6 +229,8 @@ def upgrade():
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('environment_roles',
|
||||
sa.Column('time_created', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('time_updated', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('uuid_generate_v4()'), nullable=False),
|
||||
sa.Column('environment_id', postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.Column('role', sa.String(), nullable=True),
|
||||
@ -237,10 +255,10 @@ def downgrade():
|
||||
op.drop_index('environments_role_user_environment', table_name='environment_roles')
|
||||
op.drop_table('environment_roles')
|
||||
op.drop_table('environments')
|
||||
op.drop_index('workspace_role_user_workspace', table_name='workspace_role')
|
||||
op.drop_index(op.f('ix_workspace_role_workspace_id'), table_name='workspace_role')
|
||||
op.drop_index(op.f('ix_workspace_role_user_id'), table_name='workspace_role')
|
||||
op.drop_table('workspace_role')
|
||||
op.drop_index('workspace_role_user_workspace', table_name='workspace_roles')
|
||||
op.drop_index(op.f('ix_workspace_roles_workspace_id'), table_name='workspace_roles')
|
||||
op.drop_index(op.f('ix_workspace_roles_user_id'), table_name='workspace_roles')
|
||||
op.drop_table('workspace_roles')
|
||||
op.drop_table('request_status_events')
|
||||
op.drop_table('projects')
|
||||
op.drop_table('workspaces')
|
||||
@ -249,11 +267,11 @@ def downgrade():
|
||||
op.drop_table('requests')
|
||||
op.drop_table('request_reviews')
|
||||
op.drop_table('users')
|
||||
op.drop_table('task_order')
|
||||
op.drop_table('task_orders')
|
||||
op.drop_index(op.f('ix_roles_permissions'), table_name='roles')
|
||||
op.drop_index(op.f('ix_roles_name'), table_name='roles')
|
||||
op.drop_table('roles')
|
||||
op.drop_table('pe_number')
|
||||
op.drop_table('pe_numbers')
|
||||
op.drop_table('attachments')
|
||||
# ### end Alembic commands ###
|
||||
|
@ -1,7 +1,7 @@
|
||||
from sqlalchemy import Column, Integer, String
|
||||
from flask import current_app as app
|
||||
|
||||
from atst.models import Base
|
||||
from atst.models import Base, types, mixins
|
||||
from atst.database import db
|
||||
from atst.uploader import UploadError
|
||||
|
||||
@ -10,10 +10,10 @@ class AttachmentError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Attachment(Base):
|
||||
class Attachment(Base, mixins.TimestampsMixin):
|
||||
__tablename__ = "attachments"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
id = types.Id()
|
||||
filename = Column(String)
|
||||
object_name = Column(String, unique=True)
|
||||
|
||||
|
@ -3,18 +3,17 @@ from sqlalchemy import Index, ForeignKey, Column, String
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from atst.models import Base
|
||||
from .types import Id
|
||||
from atst.models import Base, types, mixins
|
||||
|
||||
|
||||
class CSPRole(Enum):
|
||||
NONSENSE_ROLE = "nonesense_role"
|
||||
|
||||
|
||||
class EnvironmentRole(Base):
|
||||
class EnvironmentRole(Base, mixins.TimestampsMixin):
|
||||
__tablename__ = "environment_roles"
|
||||
|
||||
id = Id()
|
||||
id = types.Id()
|
||||
environment_id = Column(UUID(as_uuid=True), ForeignKey("environments.id"))
|
||||
environment = relationship("Environment", backref="roles")
|
||||
|
||||
|
@ -4,7 +4,7 @@ from atst.models import Base
|
||||
|
||||
|
||||
class PENumber(Base):
|
||||
__tablename__ = "pe_number"
|
||||
__tablename__ = "pe_numbers"
|
||||
|
||||
number = Column(String, primary_key=True)
|
||||
description = Column(String)
|
||||
|
@ -2,8 +2,7 @@ from sqlalchemy import Column, func, ForeignKey
|
||||
from sqlalchemy.types import DateTime
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from atst.models import Base
|
||||
from atst.models.types import Id
|
||||
from atst.models import Base, types, mixins
|
||||
from atst.models.request_status_event import RequestStatus
|
||||
from atst.utils import first_or_none
|
||||
from atst.models.request_revision import RequestRevision
|
||||
@ -25,10 +24,10 @@ def update_dict_with_properties(instance, body, top_level_key, properties):
|
||||
return body
|
||||
|
||||
|
||||
class Request(Base):
|
||||
class Request(Base, mixins.TimestampsMixin):
|
||||
__tablename__ = "requests"
|
||||
|
||||
id = Id()
|
||||
id = types.Id()
|
||||
time_created = Column(DateTime(timezone=True), server_default=func.now())
|
||||
status_events = relationship(
|
||||
"RequestStatusEvent", backref="request", order_by="RequestStatusEvent.sequence"
|
||||
@ -39,7 +38,7 @@ class Request(Base):
|
||||
user_id = Column(ForeignKey("users.id"), nullable=False)
|
||||
creator = relationship("User", backref="owned_requests")
|
||||
|
||||
task_order_id = Column(ForeignKey("task_order.id"))
|
||||
task_order_id = Column(ForeignKey("task_orders.id"))
|
||||
task_order = relationship("TaskOrder")
|
||||
|
||||
revisions = relationship(
|
||||
|
@ -1,10 +1,10 @@
|
||||
from sqlalchemy import Column, String, ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from atst.models import Base, types
|
||||
from atst.models import Base, types, mixins
|
||||
|
||||
|
||||
class RequestInternalComment(Base):
|
||||
class RequestInternalComment(Base, mixins.TimestampsMixin):
|
||||
__tablename__ = "request_internal_comments"
|
||||
|
||||
id = types.Id()
|
||||
|
@ -1,13 +1,13 @@
|
||||
from sqlalchemy import Column, BigInteger, String, ForeignKey
|
||||
from sqlalchemy import Column, String, ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from atst.models import Base
|
||||
from atst.models import Base, mixins, types
|
||||
|
||||
|
||||
class RequestReview(Base):
|
||||
class RequestReview(Base, mixins.TimestampsMixin):
|
||||
__tablename__ = "request_reviews"
|
||||
|
||||
id = Column(BigInteger, primary_key=True)
|
||||
id = types.Id()
|
||||
status = relationship("RequestStatusEvent", uselist=False, back_populates="review")
|
||||
|
||||
user_id = Column(ForeignKey("users.id"), nullable=False)
|
||||
|
@ -5,7 +5,7 @@ from sqlalchemy.types import DateTime, BigInteger
|
||||
from sqlalchemy.schema import Sequence
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
from atst.models import Base
|
||||
from atst.models import Base, mixins
|
||||
from atst.models.types import Id
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@ class RequestStatus(Enum):
|
||||
DELETED = "Deleted"
|
||||
|
||||
|
||||
class RequestStatusEvent(Base):
|
||||
class RequestStatusEvent(Base, mixins.TimestampsMixin):
|
||||
__tablename__ = "request_status_events"
|
||||
|
||||
id = Id()
|
||||
|
@ -2,14 +2,13 @@ from sqlalchemy import String, Column
|
||||
from sqlalchemy.dialects.postgresql import ARRAY
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
|
||||
from atst.models import Base
|
||||
from .types import Id
|
||||
from atst.models import Base, types, mixins
|
||||
|
||||
|
||||
class Role(Base):
|
||||
class Role(Base, mixins.TimestampsMixin):
|
||||
__tablename__ = "roles"
|
||||
|
||||
id = Id()
|
||||
id = types.Id()
|
||||
name = Column(String, index=True, unique=True)
|
||||
description = Column(String)
|
||||
permissions = Column(ARRAY(String), index=True, server_default="{}")
|
||||
|
@ -3,7 +3,7 @@ from enum import Enum
|
||||
from sqlalchemy import Column, Integer, String, ForeignKey, Enum as SQLAEnum, Date
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from atst.models import Base
|
||||
from atst.models import Base, types, mixins
|
||||
|
||||
|
||||
class Source(Enum):
|
||||
@ -18,10 +18,10 @@ class FundingType(Enum):
|
||||
OTHER = "OTHER"
|
||||
|
||||
|
||||
class TaskOrder(Base):
|
||||
__tablename__ = "task_order"
|
||||
class TaskOrder(Base, mixins.TimestampsMixin):
|
||||
__tablename__ = "task_orders"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
id = types.Id()
|
||||
number = Column(String, unique=True)
|
||||
source = Column(SQLAEnum(Source, native_enum=False))
|
||||
funding_type = Column(SQLAEnum(FundingType, native_enum=False))
|
||||
|
@ -2,15 +2,14 @@ from sqlalchemy import String, ForeignKey, Column
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
from atst.models import Base
|
||||
from .types import Id
|
||||
from atst.models import Base, types, mixins
|
||||
from atst.models.permissions import Permissions
|
||||
|
||||
|
||||
class User(Base):
|
||||
class User(Base, mixins.TimestampsMixin):
|
||||
__tablename__ = "users"
|
||||
|
||||
id = Id()
|
||||
id = types.Id()
|
||||
username = Column(String)
|
||||
atat_role_id = Column(UUID(as_uuid=True), ForeignKey("roles.id"))
|
||||
|
||||
|
@ -2,12 +2,12 @@ from sqlalchemy import Index, ForeignKey, Column
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from atst.models import Base
|
||||
from atst.models import Base, mixins
|
||||
from .types import Id
|
||||
|
||||
|
||||
class WorkspaceRole(Base):
|
||||
__tablename__ = "workspace_role"
|
||||
class WorkspaceRole(Base, mixins.TimestampsMixin):
|
||||
__tablename__ = "workspace_roles"
|
||||
|
||||
id = Id()
|
||||
workspace_id = Column(UUID(as_uuid=True), ForeignKey("workspaces.id"), index=True)
|
||||
|
@ -1,12 +1,11 @@
|
||||
from atst.models.task_order import TaskOrder
|
||||
|
||||
from tests.factories import TaskOrderFactory
|
||||
from tests.assert_util import dict_contains
|
||||
|
||||
|
||||
def test_as_dictionary():
|
||||
data = TaskOrderFactory.dictionary()
|
||||
real_task_order = TaskOrderFactory.create(**data)
|
||||
assert real_task_order.to_dictionary() == data
|
||||
assert dict_contains(real_task_order.to_dictionary(), data)
|
||||
|
||||
|
||||
def test_budget():
|
||||
|
Loading…
x
Reference in New Issue
Block a user