-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Review system and change history (#44)
* Checked out code related to traceability from crazyscientist/devel * Require authentication/authorization for reviewing requests * Require appropriate permissions for creating and reviewing requests * Removed obsolete model `BoundFK` * Replaced `bound_fk` and `bind_to_schema` with `bound_schema_id` * Format dates in entity lists according to locale This pull request is based on work by @commanderprice. Thank you for your contribution.
- Loading branch information
1 parent
b6f0e42
commit 412d3a1
Showing
55 changed files
with
4,942 additions
and
2,406 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
"""review | ||
Revision ID: 5e7b010c57b8 | ||
Revises: 5c72f33e6b82 | ||
Create Date: 2022-01-26 13:49:15.993571 | ||
""" | ||
from alembic import op | ||
import sqlalchemy as sa | ||
|
||
|
||
# revision identifiers, used by Alembic. | ||
revision = '5e7b010c57b8' | ||
down_revision = '5c72f33e6b82' | ||
branch_labels = None | ||
depends_on = None | ||
|
||
|
||
def upgrade(): | ||
# ### commands auto generated by Alembic - please adjust! ### | ||
op.create_table('change_values_bool', | ||
sa.Column('id', sa.Integer(), nullable=False), | ||
sa.Column('old_value', sa.Boolean(), nullable=True), | ||
sa.Column('new_value', sa.Boolean(), nullable=True), | ||
sa.PrimaryKeyConstraint('id') | ||
) | ||
op.create_index(op.f('ix_change_values_bool_id'), 'change_values_bool', ['id'], unique=False) | ||
op.create_table('change_values_date', | ||
sa.Column('id', sa.Integer(), nullable=False), | ||
sa.Column('old_value', sa.Date(), nullable=True), | ||
sa.Column('new_value', sa.Date(), nullable=True), | ||
sa.PrimaryKeyConstraint('id') | ||
) | ||
op.create_index(op.f('ix_change_values_date_id'), 'change_values_date', ['id'], unique=False) | ||
op.create_table('change_values_datetime', | ||
sa.Column('id', sa.Integer(), nullable=False), | ||
sa.Column('old_value', sa.DateTime(timezone=True), nullable=True), | ||
sa.Column('new_value', sa.DateTime(timezone=True), nullable=True), | ||
sa.PrimaryKeyConstraint('id') | ||
) | ||
op.create_index(op.f('ix_change_values_datetime_id'), 'change_values_datetime', ['id'], unique=False) | ||
op.create_table('change_values_fk', | ||
sa.Column('id', sa.Integer(), nullable=False), | ||
sa.Column('old_value', sa.Integer(), nullable=True), | ||
sa.Column('new_value', sa.Integer(), nullable=True), | ||
sa.PrimaryKeyConstraint('id') | ||
) | ||
op.create_index(op.f('ix_change_values_fk_id'), 'change_values_fk', ['id'], unique=False) | ||
op.create_table('change_values_float', | ||
sa.Column('id', sa.Integer(), nullable=False), | ||
sa.Column('old_value', sa.Float(), nullable=True), | ||
sa.Column('new_value', sa.Float(), nullable=True), | ||
sa.PrimaryKeyConstraint('id') | ||
) | ||
op.create_index(op.f('ix_change_values_float_id'), 'change_values_float', ['id'], unique=False) | ||
op.create_table('change_values_int', | ||
sa.Column('id', sa.Integer(), nullable=False), | ||
sa.Column('old_value', sa.Integer(), nullable=True), | ||
sa.Column('new_value', sa.Integer(), nullable=True), | ||
sa.PrimaryKeyConstraint('id') | ||
) | ||
op.create_index(op.f('ix_change_values_int_id'), 'change_values_int', ['id'], unique=False) | ||
op.create_table('change_values_str', | ||
sa.Column('id', sa.Integer(), nullable=False), | ||
sa.Column('old_value', sa.String(), nullable=True), | ||
sa.Column('new_value', sa.String(), nullable=True), | ||
sa.PrimaryKeyConstraint('id') | ||
) | ||
op.create_index(op.f('ix_change_values_str_id'), 'change_values_str', ['id'], unique=False) | ||
op.create_table('change_requests', | ||
sa.Column('id', sa.Integer(), nullable=False), | ||
sa.Column('created_by_user_id', sa.Integer(), nullable=False), | ||
sa.Column('reviewed_by_user_id', sa.Integer(), nullable=True), | ||
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False), | ||
sa.Column('reviewed_at', sa.DateTime(timezone=True), nullable=True), | ||
sa.Column('status', sa.Enum('PENDING', 'DECLINED', 'APPROVED', name='changestatus'), nullable=False), | ||
sa.Column('comment', sa.String(length=1024), nullable=True), | ||
sa.Column('object_type', sa.Enum('SCHEMA', 'ENTITY', name='editableobjecttype'), nullable=False), | ||
sa.Column('object_id', sa.Integer(), nullable=True), | ||
sa.Column('change_type', sa.Enum('CREATE', 'UPDATE', 'DELETE', name='changetype'), nullable=False), | ||
sa.CheckConstraint("object_id IS NOT NULL OR (change_type = 'CREATE' AND status <> 'APPROVED')"), | ||
sa.ForeignKeyConstraint(['created_by_user_id'], ['users.id'], ), | ||
sa.ForeignKeyConstraint(['reviewed_by_user_id'], ['users.id'], ), | ||
sa.PrimaryKeyConstraint('id') | ||
) | ||
op.create_table('changes', | ||
sa.Column('id', sa.Integer(), nullable=False), | ||
sa.Column('change_request_id', sa.Integer(), nullable=False), | ||
sa.Column('attribute_id', sa.Integer(), nullable=True), | ||
sa.Column('object_id', sa.Integer(), nullable=True), | ||
sa.Column('value_id', sa.Integer(), nullable=False), | ||
sa.Column('content_type', sa.Enum('ATTRIBUTE', 'ATTRIBUTE_DEFINITION', 'ENTITY', 'SCHEMA', name='contenttype'), nullable=False), | ||
sa.Column('change_type', sa.Enum('CREATE', 'UPDATE', 'DELETE', name='changetype'), nullable=False), | ||
sa.Column('field_name', sa.String(), nullable=True), | ||
sa.Column('data_type', sa.Enum('STR', 'BOOL', 'INT', 'FLOAT', 'FK', 'DT', 'DATE', name='changeattrtype'), nullable=False), | ||
sa.CheckConstraint('NOT(attribute_id IS NULL AND field_name IS NULL) AND NOT (attribute_id IS NOT NULL AND field_name IS NOT NULL)'), | ||
sa.ForeignKeyConstraint(['attribute_id'], ['attributes.id'], ), | ||
sa.ForeignKeyConstraint(['change_request_id'], ['change_requests.id'], ), | ||
sa.PrimaryKeyConstraint('id') | ||
) | ||
op.create_table('values_date', | ||
sa.Column('id', sa.Integer(), nullable=False), | ||
sa.Column('value', sa.Date(), nullable=True), | ||
sa.Column('entity_id', sa.Integer(), nullable=True), | ||
sa.Column('attribute_id', sa.Integer(), nullable=True), | ||
sa.ForeignKeyConstraint(['attribute_id'], ['attributes.id'], ), | ||
sa.ForeignKeyConstraint(['entity_id'], ['entities.id'], ), | ||
sa.PrimaryKeyConstraint('id') | ||
) | ||
op.create_index(op.f('ix_values_date_id'), 'values_date', ['id'], unique=False) | ||
op.add_column('schemas', sa.Column('reviewable', sa.Boolean(), nullable=True)) | ||
# ### end Alembic commands ### | ||
|
||
|
||
def downgrade(): | ||
# ### commands auto generated by Alembic - please adjust! ### | ||
op.drop_column('schemas', 'reviewable') | ||
op.drop_index(op.f('ix_values_date_id'), table_name='values_date') | ||
op.drop_table('values_date') | ||
op.drop_table('changes') | ||
op.drop_table('change_requests') | ||
op.drop_index(op.f('ix_change_values_str_id'), table_name='change_values_str') | ||
op.drop_table('change_values_str') | ||
op.drop_index(op.f('ix_change_values_int_id'), table_name='change_values_int') | ||
op.drop_table('change_values_int') | ||
op.drop_index(op.f('ix_change_values_float_id'), table_name='change_values_float') | ||
op.drop_table('change_values_float') | ||
op.drop_index(op.f('ix_change_values_fk_id'), table_name='change_values_fk') | ||
op.drop_table('change_values_fk') | ||
op.drop_index(op.f('ix_change_values_datetime_id'), table_name='change_values_datetime') | ||
op.drop_table('change_values_datetime') | ||
op.drop_index(op.f('ix_change_values_date_id'), table_name='change_values_date') | ||
op.drop_table('change_values_date') | ||
op.drop_index(op.f('ix_change_values_bool_id'), table_name='change_values_bool') | ||
op.drop_table('change_values_bool') | ||
# ### end Alembic commands ### |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
"""Bound FK | ||
Revision ID: ef9a7b3217c3 | ||
Revises: 5e7b010c57b8 | ||
Create Date: 2022-02-04 08:55:06.006728 | ||
""" | ||
from alembic import op | ||
import sqlalchemy as sa | ||
|
||
|
||
# revision identifiers, used by Alembic. | ||
revision = 'ef9a7b3217c3' | ||
down_revision = '5e7b010c57b8' | ||
branch_labels = None | ||
depends_on = None | ||
|
||
|
||
def upgrade(): | ||
# ### commands auto generated by Alembic - please adjust! ### | ||
op.drop_index('ix_bound_foreign_keys_id', table_name='bound_foreign_keys') | ||
op.drop_table('bound_foreign_keys') | ||
op.add_column('attr_definitions', sa.Column('bound_schema_id', sa.Integer(), nullable=True)) | ||
op.create_foreign_key(None, 'attr_definitions', 'schemas', ['bound_schema_id'], ['id']) | ||
# ### end Alembic commands ### | ||
|
||
|
||
def downgrade(): | ||
# ### commands auto generated by Alembic - please adjust! ### | ||
op.drop_constraint(None, 'attr_definitions', type_='foreignkey') | ||
op.drop_column('attr_definitions', 'bound_schema_id') | ||
op.create_table('bound_foreign_keys', | ||
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), | ||
sa.Column('attr_def_id', sa.INTEGER(), autoincrement=False, nullable=True), | ||
sa.Column('schema_id', sa.INTEGER(), autoincrement=False, nullable=True), | ||
sa.ForeignKeyConstraint(['attr_def_id'], ['attr_definitions.id'], name='bound_foreign_keys_attr_def_id_fkey'), | ||
sa.ForeignKeyConstraint(['schema_id'], ['schemas.id'], name='bound_foreign_keys_schema_id_fkey'), | ||
sa.PrimaryKeyConstraint('id', name='bound_foreign_keys_pkey') | ||
) | ||
op.create_index('ix_bound_foreign_keys_id', 'bound_foreign_keys', ['id'], unique=False) | ||
# ### end Alembic commands ### |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
from sqlalchemy import Column, Integer, String, ForeignKey, Boolean, Enum | ||
from sqlalchemy.orm import backref, relationship | ||
from sqlalchemy.sql.schema import UniqueConstraint | ||
|
||
from ..base_models import Base | ||
from .enum import RecipientType, PermissionTargetType, PermissionType | ||
|
||
class Group(Base): | ||
__tablename__ = 'groups' | ||
|
||
id = Column(Integer, primary_key=True) | ||
name = Column(String(128), unique=True, nullable=False) | ||
parent_id = Column(Integer, ForeignKey('groups.id')) | ||
parent = relationship('Group', remote_side=[id], backref=backref('subgroups')) | ||
|
||
def __str__(self): | ||
return self.name | ||
|
||
|
||
class User(Base): | ||
__tablename__ = 'users' | ||
id = Column(Integer, primary_key=True) | ||
username = Column(String(128), unique=True, nullable=False) | ||
email = Column(String(128), unique=True, nullable=False) | ||
password = Column(String, nullable=True) | ||
firstname = Column(String(128), nullable=True) | ||
lastname = Column(String(128), nullable=True) | ||
is_active = Column(Boolean, default=True) | ||
|
||
def __str__(self): | ||
return self.username | ||
|
||
|
||
class Permission(Base): | ||
__tablename__ = 'permissions' | ||
id = Column(Integer, primary_key=True) | ||
recipient_type = Column(Enum(RecipientType), nullable=False) | ||
recipient_id = Column(Integer, nullable=False) | ||
obj_type = Column(Enum(PermissionTargetType), nullable=True) | ||
obj_id = Column(Integer, nullable=True) | ||
permission = Column(Enum(PermissionType), nullable=False) | ||
|
||
user = relationship('User', | ||
primaryjoin="and_(User.id == foreign(Permission.recipient_id), " | ||
"Permission.recipient_type == 'USER')", | ||
overlaps="group") | ||
group = relationship('Group', | ||
primaryjoin="and_(Group.id == foreign(Permission.recipient_id), " | ||
"Permission.recipient_type == 'GROUP')", | ||
overlaps="user") | ||
schema = relationship('Schema', | ||
primaryjoin="and_(Schema.id == foreign(Permission.obj_id), " | ||
"Permission.obj_type == 'SCHEMA')", | ||
overlaps="entity, managed_group" | ||
) | ||
entity = relationship('Entity', | ||
primaryjoin="and_(Entity.id == foreign(Permission.obj_id), " | ||
"Permission.obj_type == 'ENTITY')", | ||
overlaps="schema, managed_group" | ||
) | ||
managed_group = relationship('Group', | ||
primaryjoin="and_(Group.id == foreign(Permission.obj_id), " | ||
"Permission.obj_type == 'GROUP')", | ||
overlaps="schema, entity" | ||
) | ||
|
||
__table_args__ = ( | ||
UniqueConstraint('recipient_type', 'recipient_id', 'obj_type', 'obj_id', 'permission'), | ||
) | ||
|
||
@property | ||
def recipient_name(self): | ||
return self.user.username if self.recipient_type == RecipientType.USER else self.group.name | ||
|
||
|
||
class UserGroup(Base): | ||
__tablename__ = 'user_groups' | ||
id = Column(Integer, primary_key=True) | ||
group_id = Column(Integer, ForeignKey('groups.id'), nullable=False) | ||
user_id = Column(Integer, ForeignKey('users.id'), nullable=False) | ||
|
||
group = relationship('Group') | ||
user = relationship('User') | ||
|
||
__table_args__ = ( | ||
UniqueConstraint('user_id', 'group_id'), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import typing | ||
from typing import NamedTuple | ||
|
||
from .database import Base | ||
|
||
from sqlalchemy import Column, Integer, ForeignKey | ||
from sqlalchemy.ext.declarative import declared_attr | ||
from sqlalchemy.orm import relationship | ||
|
||
|
||
class Value(Base): | ||
__abstract__ = True | ||
|
||
id = Column(Integer, primary_key=True, index=True) | ||
|
||
@declared_attr | ||
def entity_id(cls): | ||
return Column(Integer, ForeignKey('entities.id')) | ||
|
||
@declared_attr | ||
def attribute_id(cls): | ||
return Column(Integer, ForeignKey('attributes.id')) | ||
|
||
@declared_attr | ||
def entity(cls): | ||
return relationship('Entity') | ||
|
||
|
||
class Mapping(NamedTuple): | ||
model: Value | ||
converter: type | ||
filters: list = [] | ||
|
Oops, something went wrong.