mirror of https://github.com/logos-co/open-law.git
permissions models
This commit is contained in:
parent
cfd05b8cd9
commit
102a7f3577
|
@ -11,6 +11,7 @@
|
||||||
"**/.venv/*/**": true
|
"**/.venv/*/**": true
|
||||||
},
|
},
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
|
"backref",
|
||||||
"bookname",
|
"bookname",
|
||||||
"Btns",
|
"Btns",
|
||||||
"flowbite",
|
"flowbite",
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
from app import models as m
|
from app import models as m
|
||||||
|
from app.logger import log
|
||||||
|
|
||||||
|
|
||||||
def get_or_create_permission(access: int, entity: m.Permission.Entity):
|
def get_or_create_permission(access: int, entity_type: m.Permission.Entity):
|
||||||
permission: m.Permission = m.Permission.query.filter_by(
|
permission: m.Permission = m.Permission.query.filter_by(
|
||||||
access=access, entity=entity
|
access=access, entity_type=entity_type
|
||||||
).first()
|
).first()
|
||||||
if not permission:
|
if not permission:
|
||||||
permission: m.Permission = m.Permission(access=access, entity=entity).save()
|
log(log.INFO, "Create permission [%d] for entity [%s]", access, entity_type)
|
||||||
|
permission: m.Permission = m.Permission(
|
||||||
|
access=access, entity_type=entity_type
|
||||||
|
).save()
|
||||||
return permission
|
return permission
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
from app import models as m
|
||||||
|
from app.logger import log
|
||||||
|
from .get_or_create_permission import get_or_create_permission
|
||||||
|
|
||||||
|
|
||||||
|
def create_moderator_group():
|
||||||
|
log(log.INFO, "Create moderator access group")
|
||||||
|
group: m.AccessGroup = m.AccessGroup(name="moderator").save()
|
||||||
|
|
||||||
|
interpretation_DA = get_or_create_permission(
|
||||||
|
access=m.Permission.Access.D | m.Permission.Access.A,
|
||||||
|
entity_type=m.Permission.Entity.INTERPRETATION,
|
||||||
|
)
|
||||||
|
comment_DA = get_or_create_permission(
|
||||||
|
access=m.Permission.Access.D | m.Permission.Access.A,
|
||||||
|
entity_type=m.Permission.Entity.COMMENT,
|
||||||
|
)
|
||||||
|
|
||||||
|
log(log.INFO, "Add permission [%d] to group[%d]", interpretation_DA.id, group.id)
|
||||||
|
m.PermissionAccessGroups(
|
||||||
|
permission_id=interpretation_DA.id, access_group_id=group.id
|
||||||
|
).save()
|
||||||
|
|
||||||
|
log(log.INFO, "Add permission [%d] to group[%d]", comment_DA.id, group.id)
|
||||||
|
m.PermissionAccessGroups(
|
||||||
|
permission_id=comment_DA.id, access_group_id=group.id
|
||||||
|
).save()
|
||||||
|
|
||||||
|
return group
|
|
@ -18,4 +18,8 @@ from .permission import (
|
||||||
AccessGroup,
|
AccessGroup,
|
||||||
UserAccessGroups,
|
UserAccessGroups,
|
||||||
PermissionAccessGroups,
|
PermissionAccessGroups,
|
||||||
|
BookAccessGroups,
|
||||||
|
CollectionAccessGroups,
|
||||||
|
SectionAccessGroups,
|
||||||
|
InterpretationAccessGroups,
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,6 +19,13 @@ class Book(BaseModel):
|
||||||
stars = db.relationship("User", secondary="books_stars", back_populates="stars")
|
stars = db.relationship("User", secondary="books_stars", back_populates="stars")
|
||||||
contributors = db.relationship("BookContributor")
|
contributors = db.relationship("BookContributor")
|
||||||
versions = db.relationship("BookVersion", order_by="asc(BookVersion.id)")
|
versions = db.relationship("BookVersion", order_by="asc(BookVersion.id)")
|
||||||
|
list_access_groups = db.relationship(
|
||||||
|
"AccessGroup"
|
||||||
|
) # all access_groups in current book(in nested entities)
|
||||||
|
access_groups = db.relationship(
|
||||||
|
"AccessGroup",
|
||||||
|
secondary="books_access_groups",
|
||||||
|
) # access_groups related to current entity
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.id}: {self.label}>"
|
return f"<{self.id}: {self.label}>"
|
||||||
|
|
|
@ -25,6 +25,10 @@ class Collection(BaseModel):
|
||||||
order_by="asc(Collection.id)",
|
order_by="asc(Collection.id)",
|
||||||
)
|
)
|
||||||
sections = db.relationship("Section")
|
sections = db.relationship("Section")
|
||||||
|
access_groups = db.relationship(
|
||||||
|
"AccessGroup",
|
||||||
|
secondary="collections_access_groups",
|
||||||
|
) # access_groups related to current entity
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.id}: {self.label}>"
|
return f"<{self.id}: {self.label}>"
|
||||||
|
|
|
@ -25,6 +25,10 @@ class Interpretation(BaseModel):
|
||||||
secondary="interpretation_tags",
|
secondary="interpretation_tags",
|
||||||
back_populates="interpretations",
|
back_populates="interpretations",
|
||||||
)
|
)
|
||||||
|
access_groups = db.relationship(
|
||||||
|
"AccessGroup",
|
||||||
|
secondary="interpretations_access_groups",
|
||||||
|
) # access_groups related to current entity
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def vote_count(self):
|
def vote_count(self):
|
||||||
|
|
|
@ -3,6 +3,10 @@ from .access_group import AccessGroup
|
||||||
from .permission import Permission
|
from .permission import Permission
|
||||||
from .user_access_groups import UserAccessGroups
|
from .user_access_groups import UserAccessGroups
|
||||||
from .permission_access_groups import PermissionAccessGroups
|
from .permission_access_groups import PermissionAccessGroups
|
||||||
|
from .book_access_groups import BookAccessGroups
|
||||||
|
from .collection_access_groups import CollectionAccessGroups
|
||||||
|
from .section_access_groups import SectionAccessGroups
|
||||||
|
from .interpretation_access_groups import InterpretationAccessGroups
|
||||||
|
|
||||||
# access groups
|
# access groups
|
||||||
# moderators(by default empty) -> root collection -> CRUD Interpretation, Comment
|
# moderators(by default empty) -> root collection -> CRUD Interpretation, Comment
|
||||||
|
@ -26,6 +30,7 @@ from .permission_access_groups import PermissionAccessGroups
|
||||||
|
|
||||||
|
|
||||||
# Book
|
# Book
|
||||||
|
# Version:
|
||||||
# Root Collection
|
# Root Collection
|
||||||
# Collection A
|
# Collection A
|
||||||
# Section
|
# Section
|
||||||
|
@ -40,3 +45,13 @@ from .permission_access_groups import PermissionAccessGroups
|
||||||
|
|
||||||
# If the user has CRUD access to Collection B it means that
|
# If the user has CRUD access to Collection B it means that
|
||||||
# it has access to all nested entities(SubCollection B.1/B.2, Sections)
|
# it has access to all nested entities(SubCollection B.1/B.2, Sections)
|
||||||
|
|
||||||
|
|
||||||
|
# 1) Create moderator_AG and editor_AG on book create
|
||||||
|
# 2) Inherit parent's access groups
|
||||||
|
|
||||||
|
# TODO many to many
|
||||||
|
# book -> access_group
|
||||||
|
# collections -> access_group
|
||||||
|
# section -> access_group
|
||||||
|
# interpretation -> access_group
|
||||||
|
|
|
@ -5,9 +5,13 @@ from app.models.utils import BaseModel
|
||||||
class AccessGroup(BaseModel):
|
class AccessGroup(BaseModel):
|
||||||
__tablename__ = "access_groups"
|
__tablename__ = "access_groups"
|
||||||
|
|
||||||
name = db.Column(db.String(32), unique=True, nullable=False)
|
name = db.Column(db.String(32), nullable=False)
|
||||||
|
|
||||||
|
# Foreign Keys
|
||||||
|
book_id = db.Column(db.Integer, db.ForeignKey("books.id"))
|
||||||
|
|
||||||
# Relationships
|
# Relationships
|
||||||
|
book = db.relationship("Book", viewonly=True)
|
||||||
permissions = db.relationship(
|
permissions = db.relationship(
|
||||||
"Permission",
|
"Permission",
|
||||||
secondary="permissions_access_groups",
|
secondary="permissions_access_groups",
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
from app import db
|
||||||
|
from app.models.utils import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class BookAccessGroups(BaseModel):
|
||||||
|
__tablename__ = "books_access_groups"
|
||||||
|
|
||||||
|
# Foreign keys
|
||||||
|
book_id = db.Column(db.Integer, db.ForeignKey("books.id"))
|
||||||
|
access_group_id = db.Column(db.Integer, db.ForeignKey("access_groups.id"))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<b:{self.book_id} to a_g:{self.access_group_id}"
|
|
@ -0,0 +1,13 @@
|
||||||
|
from app import db
|
||||||
|
from app.models.utils import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionAccessGroups(BaseModel):
|
||||||
|
__tablename__ = "collections_access_groups"
|
||||||
|
|
||||||
|
# Foreign keys
|
||||||
|
collection_id = db.Column(db.Integer, db.ForeignKey("collections.id"))
|
||||||
|
access_group_id = db.Column(db.Integer, db.ForeignKey("access_groups.id"))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<c:{self.collection_id} to a_g:{self.access_group_id}"
|
|
@ -0,0 +1,13 @@
|
||||||
|
from app import db
|
||||||
|
from app.models.utils import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class InterpretationAccessGroups(BaseModel):
|
||||||
|
__tablename__ = "interpretations_access_groups"
|
||||||
|
|
||||||
|
# Foreign keys
|
||||||
|
interpretation_id = db.Column(db.Integer, db.ForeignKey("interpretations.id"))
|
||||||
|
access_group_id = db.Column(db.Integer, db.ForeignKey("access_groups.id"))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<c:{self.interpretation_id} to a_g:{self.access_group_id}"
|
|
@ -8,10 +8,10 @@ class Permission(BaseModel):
|
||||||
__tablename__ = "permissions"
|
__tablename__ = "permissions"
|
||||||
|
|
||||||
class Access(IntEnum):
|
class Access(IntEnum):
|
||||||
C = 1 # 0b0001
|
C = 1 # 0b0001 - Create
|
||||||
R = 2 # 0b0010
|
U = 2 # 0b0010 - Update
|
||||||
U = 4 # 0b0100
|
D = 4 # 0b0100 - Delete
|
||||||
D = 8 # 0b1000
|
A = 8 # 0b1000 - Approve
|
||||||
# sum = 0b1111
|
# sum = 0b1111
|
||||||
|
|
||||||
class Entity(IntEnum):
|
class Entity(IntEnum):
|
||||||
|
@ -22,8 +22,8 @@ class Permission(BaseModel):
|
||||||
INTERPRETATION = 4
|
INTERPRETATION = 4
|
||||||
COMMENT = 5
|
COMMENT = 5
|
||||||
|
|
||||||
access = db.Column(db.Integer(), default=Access.C | Access.R | Access.U | Access.D)
|
access = db.Column(db.Integer(), default=Access.C | Access.U | Access.D | Access.A)
|
||||||
entity = db.Column(db.Enum(Entity), default=Entity.UNKNOWN)
|
entity_type = db.Column(db.Enum(Entity), default=Entity.UNKNOWN)
|
||||||
|
|
||||||
# Relationships
|
# Relationships
|
||||||
access_groups = db.relationship(
|
access_groups = db.relationship(
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
from app import db
|
||||||
|
from app.models.utils import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class SectionAccessGroups(BaseModel):
|
||||||
|
__tablename__ = "sections_access_groups"
|
||||||
|
|
||||||
|
# Foreign keys
|
||||||
|
section_id = db.Column(db.Integer, db.ForeignKey("sections.id"))
|
||||||
|
access_group_id = db.Column(db.Integer, db.ForeignKey("access_groups.id"))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<s:{self.section_id} to a_g:{self.access_group_id}"
|
|
@ -26,6 +26,10 @@ class Section(BaseModel):
|
||||||
interpretations = db.relationship(
|
interpretations = db.relationship(
|
||||||
"Interpretation", viewonly=True, order_by="desc(Interpretation.id)"
|
"Interpretation", viewonly=True, order_by="desc(Interpretation.id)"
|
||||||
)
|
)
|
||||||
|
access_groups = db.relationship(
|
||||||
|
"AccessGroup",
|
||||||
|
secondary="sections_access_groups",
|
||||||
|
) # access_groups related to current entity
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path(self):
|
def path(self):
|
||||||
|
|
|
@ -1137,10 +1137,6 @@ input:checked + .toggle-bg {
|
||||||
top: 2.5rem;
|
top: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-3 {
|
|
||||||
top: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-32 {
|
.top-32 {
|
||||||
top: 8rem;
|
top: 8rem;
|
||||||
}
|
}
|
||||||
|
@ -1149,8 +1145,8 @@ input:checked + .toggle-bg {
|
||||||
top: 11rem;
|
top: 11rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.-z-10 {
|
.top-3 {
|
||||||
z-index: -10;
|
top: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.z-0 {
|
.z-0 {
|
||||||
|
@ -1189,6 +1185,10 @@ input:checked + .toggle-bg {
|
||||||
z-index: 55;
|
z-index: 55;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.-z-10 {
|
||||||
|
z-index: -10;
|
||||||
|
}
|
||||||
|
|
||||||
.col-span-6 {
|
.col-span-6 {
|
||||||
grid-column: span 6 / span 6;
|
grid-column: span 6 / span 6;
|
||||||
}
|
}
|
||||||
|
@ -1438,6 +1438,10 @@ input:checked + .toggle-bg {
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.h-40 {
|
||||||
|
height: 10rem;
|
||||||
|
}
|
||||||
|
|
||||||
.h-5 {
|
.h-5 {
|
||||||
height: 1.25rem;
|
height: 1.25rem;
|
||||||
}
|
}
|
||||||
|
@ -1446,18 +1450,10 @@ input:checked + .toggle-bg {
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h-64 {
|
|
||||||
height: 16rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-8 {
|
.h-8 {
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h-80 {
|
|
||||||
height: 20rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-9 {
|
.h-9 {
|
||||||
height: 2.25rem;
|
height: 2.25rem;
|
||||||
}
|
}
|
||||||
|
@ -1483,6 +1479,14 @@ input:checked + .toggle-bg {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.h-64 {
|
||||||
|
height: 16rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-80 {
|
||||||
|
height: 20rem;
|
||||||
|
}
|
||||||
|
|
||||||
.max-h-40 {
|
.max-h-40 {
|
||||||
max-height: 10rem;
|
max-height: 10rem;
|
||||||
}
|
}
|
||||||
|
@ -1591,14 +1595,6 @@ input:checked + .toggle-bg {
|
||||||
max-width: 72rem;
|
max-width: 72rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.max-w-\[230\] {
|
|
||||||
max-width: 230;
|
|
||||||
}
|
|
||||||
|
|
||||||
.max-w-\[280\] {
|
|
||||||
max-width: 280;
|
|
||||||
}
|
|
||||||
|
|
||||||
.max-w-full {
|
.max-w-full {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -1607,6 +1603,14 @@ input:checked + .toggle-bg {
|
||||||
max-width: 20rem;
|
max-width: 20rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.max-w-\[230\] {
|
||||||
|
max-width: 230;
|
||||||
|
}
|
||||||
|
|
||||||
|
.max-w-\[280\] {
|
||||||
|
max-width: 280;
|
||||||
|
}
|
||||||
|
|
||||||
.flex-1 {
|
.flex-1 {
|
||||||
flex: 1 1 0%;
|
flex: 1 1 0%;
|
||||||
}
|
}
|
||||||
|
@ -1632,11 +1636,6 @@ input:checked + .toggle-bg {
|
||||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
}
|
}
|
||||||
|
|
||||||
.-translate-y-6 {
|
|
||||||
--tw-translate-y: -1.5rem;
|
|
||||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
||||||
}
|
|
||||||
|
|
||||||
.-translate-y-full {
|
.-translate-y-full {
|
||||||
--tw-translate-y: -100%;
|
--tw-translate-y: -100%;
|
||||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
|
@ -1662,6 +1661,11 @@ input:checked + .toggle-bg {
|
||||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.-translate-y-6 {
|
||||||
|
--tw-translate-y: -1.5rem;
|
||||||
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
|
}
|
||||||
|
|
||||||
.rotate-180 {
|
.rotate-180 {
|
||||||
--tw-rotate: 180deg;
|
--tw-rotate: 180deg;
|
||||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
|
@ -1773,6 +1777,10 @@ input:checked + .toggle-bg {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gap-1 {
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.gap-2 {
|
.gap-2 {
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
@ -1841,12 +1849,6 @@ input:checked + .toggle-bg {
|
||||||
margin-bottom: calc(2rem * var(--tw-space-y-reverse));
|
margin-bottom: calc(2rem * var(--tw-space-y-reverse));
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
|
|
||||||
--tw-space-x-reverse: 0;
|
|
||||||
margin-right: calc(1rem * var(--tw-space-x-reverse));
|
|
||||||
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
|
|
||||||
}
|
|
||||||
|
|
||||||
.divide-y > :not([hidden]) ~ :not([hidden]) {
|
.divide-y > :not([hidden]) ~ :not([hidden]) {
|
||||||
--tw-divide-y-reverse: 0;
|
--tw-divide-y-reverse: 0;
|
||||||
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
|
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
|
||||||
|
@ -2119,6 +2121,11 @@ input:checked + .toggle-bg {
|
||||||
background-color: rgb(200 30 30 / var(--tw-bg-opacity));
|
background-color: rgb(200 30 30 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-sky-300 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(125 211 252 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.bg-slate-300 {
|
.bg-slate-300 {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(203 213 225 / var(--tw-bg-opacity));
|
background-color: rgb(203 213 225 / var(--tw-bg-opacity));
|
||||||
|
@ -2185,6 +2192,10 @@ input:checked + .toggle-bg {
|
||||||
stroke: #F05252;
|
stroke: #F05252;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.\!p-0 {
|
||||||
|
padding: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
.p-0 {
|
.p-0 {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
|
@ -2226,11 +2237,6 @@ input:checked + .toggle-bg {
|
||||||
padding-right: 0px !important;
|
padding-right: 0px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.px-0 {
|
|
||||||
padding-left: 0px;
|
|
||||||
padding-right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.px-2 {
|
.px-2 {
|
||||||
padding-left: 0.5rem;
|
padding-left: 0.5rem;
|
||||||
padding-right: 0.5rem;
|
padding-right: 0.5rem;
|
||||||
|
@ -2286,6 +2292,11 @@ input:checked + .toggle-bg {
|
||||||
padding-bottom: 1rem;
|
padding-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.px-0 {
|
||||||
|
padding-left: 0px;
|
||||||
|
padding-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.pb-3 {
|
.pb-3 {
|
||||||
padding-bottom: 0.75rem;
|
padding-bottom: 0.75rem;
|
||||||
}
|
}
|
||||||
|
@ -2466,6 +2477,11 @@ input:checked + .toggle-bg {
|
||||||
color: rgb(14 159 110 / var(--tw-text-opacity));
|
color: rgb(14 159 110 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-orange-500 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(255 90 31 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.text-red-500 {
|
.text-red-500 {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(240 82 82 / var(--tw-text-opacity));
|
color: rgb(240 82 82 / var(--tw-text-opacity));
|
||||||
|
@ -2489,6 +2505,10 @@ input:checked + .toggle-bg {
|
||||||
text-decoration-line: line-through;
|
text-decoration-line: line-through;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.\!no-underline {
|
||||||
|
text-decoration-line: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
.opacity-0 {
|
.opacity-0 {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
@ -2626,6 +2646,11 @@ input:checked + .toggle-bg {
|
||||||
background-color: rgb(155 28 28 / var(--tw-bg-opacity));
|
background-color: rgb(155 28 28 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hover\:bg-sky-400:hover {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(56 189 248 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.hover\:bg-white:hover {
|
.hover\:bg-white:hover {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||||
|
@ -2680,12 +2705,6 @@ input:checked + .toggle-bg {
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.focus\:ring-0:focus {
|
|
||||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
|
||||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
|
||||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
|
||||||
}
|
|
||||||
|
|
||||||
.focus\:ring-2:focus {
|
.focus\:ring-2:focus {
|
||||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||||
|
@ -2698,6 +2717,12 @@ input:checked + .toggle-bg {
|
||||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.focus\:ring-0:focus {
|
||||||
|
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||||
|
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||||
|
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||||
|
}
|
||||||
|
|
||||||
.focus\:ring-blue-100:focus {
|
.focus\:ring-blue-100:focus {
|
||||||
--tw-ring-opacity: 1;
|
--tw-ring-opacity: 1;
|
||||||
--tw-ring-color: rgb(225 239 254 / var(--tw-ring-opacity));
|
--tw-ring-color: rgb(225 239 254 / var(--tw-ring-opacity));
|
||||||
|
|
|
@ -4,27 +4,29 @@ from app.controllers.get_or_create_permission import get_or_create_permission
|
||||||
|
|
||||||
def test_get_or_create_permission(client):
|
def test_get_or_create_permission(client):
|
||||||
access = m.Permission.Access
|
access = m.Permission.Access
|
||||||
entity = m.Permission.Entity
|
entity_type = m.Permission.Entity
|
||||||
|
|
||||||
book_u: m.Permission = m.Permission.query.filter_by(
|
book_u: m.Permission = m.Permission.query.filter_by(
|
||||||
access=access.U, entity=entity.BOOK
|
access=access.U, entity_type=entity_type.BOOK
|
||||||
).first()
|
).first()
|
||||||
assert not book_u
|
assert not book_u
|
||||||
|
|
||||||
assert not m.Permission.query.count()
|
assert not m.Permission.query.count()
|
||||||
|
|
||||||
book_u: m.Permission = get_or_create_permission(access=access.U, entity=entity.BOOK)
|
book_u: m.Permission = get_or_create_permission(
|
||||||
|
access=access.U, entity_type=entity_type.BOOK
|
||||||
|
)
|
||||||
assert book_u
|
assert book_u
|
||||||
assert book_u.access == access.U
|
assert book_u.access == access.U
|
||||||
assert book_u.entity == entity.BOOK
|
assert book_u.entity_type == entity_type.BOOK
|
||||||
assert m.Permission.query.count() == 1
|
assert m.Permission.query.count() == 1
|
||||||
|
|
||||||
book_u: m.Permission = m.Permission.query.filter_by(
|
book_u: m.Permission = m.Permission.query.filter_by(
|
||||||
access=access.U, entity=entity.BOOK
|
access=access.U, entity_type=entity_type.BOOK
|
||||||
).first()
|
).first()
|
||||||
assert book_u
|
assert book_u
|
||||||
assert book_u.access == access.U
|
assert book_u.access == access.U
|
||||||
assert book_u.entity == entity.BOOK
|
assert book_u.entity_type == entity_type.BOOK
|
||||||
|
|
||||||
get_or_create_permission(access=access.U, entity=entity.BOOK)
|
get_or_create_permission(access=access.U, entity_type=entity_type.BOOK)
|
||||||
assert m.Permission.query.count() == 1
|
assert m.Permission.query.count() == 1
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
from app.controllers.init_access_groups import create_moderator_group
|
||||||
|
from app import models as m
|
||||||
|
|
||||||
|
|
||||||
|
def test_init_moderator_group(client):
|
||||||
|
create_moderator_group()
|
||||||
|
|
||||||
|
group: m.AccessGroup = m.AccessGroup.query.filter_by(name="moderator").first()
|
||||||
|
assert group
|
||||||
|
assert not group.users
|
||||||
|
assert group.permissions
|
||||||
|
|
||||||
|
permissions = group.permissions
|
||||||
|
|
||||||
|
access = m.Permission.Access
|
||||||
|
|
||||||
|
interpretation_DA: m.Permission = m.Permission.query.filter_by(
|
||||||
|
access=access.D | access.A, entity_type=m.Permission.Entity.INTERPRETATION
|
||||||
|
).first()
|
||||||
|
assert interpretation_DA
|
||||||
|
assert interpretation_DA in permissions
|
||||||
|
|
||||||
|
comment_DA: m.Permission = m.Permission.query.filter_by(
|
||||||
|
access=access.D | access.A, entity_type=m.Permission.Entity.COMMENT
|
||||||
|
).first()
|
||||||
|
assert comment_DA
|
||||||
|
assert comment_DA in permissions
|
||||||
|
|
||||||
|
create_moderator_group()
|
||||||
|
groups: list[m.AccessGroup] = m.AccessGroup.query.filter_by(name="moderator").all()
|
||||||
|
assert len(groups) == 2
|
Loading…
Reference in New Issue