diff --git a/app/models/models.py b/app/models/models.py deleted file mode 100644 index c2bead0..0000000 --- a/app/models/models.py +++ /dev/null @@ -1,36 +0,0 @@ -# from datetime import datetime -# from app import db, login -# from flask_login import UserMixin -# from werkzeug.security import generate_password_hash, check_password_hash - - -# # class User(UserMixin, db.Model): -# # id = db.Column(db.Integer, primary_key=True) -# # username = db.Column(db.String(64), index=True, unique=True) -# # email = db.Column(db.String(120), index=True, unique=True) -# # password_hash = db.Column(db.String(128)) -# # posts = db.relationship('Post', backref='author', lazy='dynamic') - -# # def __repr__(self): -# # return ''.format(self.username) - -# # def set_password(self, password): -# # self.password_hash = generate_password_hash(password) - -# # def check_password(self, password): -# # return check_password_hash(self.password_hash, password) - - -# # @login.user_loader -# # def load_user(id): -# # return User.query.get(int(id)) - - -# class Post(db.Model): -# id = db.Column(db.Integer, primary_key=True) -# body = db.Column(db.String(140)) -# timestamp = db.Column(db.DateTime, index=True, default=datetime.now) -# user_id = db.Column(db.Integer, db.ForeignKey('user.id')) - -# def __repr__(self): -# return ''.format(self.body) diff --git a/app/models/privs.py b/app/models/privs.py new file mode 100644 index 0000000..efedc48 --- /dev/null +++ b/app/models/privs.py @@ -0,0 +1,57 @@ +# Planète Casio v5 +# models.privs: Database models for groups and privilege management + +from app import db +from config import Config + +# Privileges are represented by strings (slugs), for instance "post-news" or +# "delete-own-posts". Belonging to a group automatically grants a user the +# privileges of that group; additionally, administrators (or any people with +# the "grant-special-privileges" privilege) can grant privileges on a per-user +# basis. + +# SpecialPrivilege: Privilege manually granted to a user +class SpecialPrivilege(db.Model): + __tablename__ = 'special_privilege' + id = db.Column(db.Integer, primary_key=True) + + # User that is granted the privilege + uid = db.Column(db.Integer, db.ForeignKey('user.id'), index=True) + # Privilege name + priv = db.Column(db.String(Config.PRIVS_MAXLEN)) + + def __repr__(self): + return f'' + +# Group: User group, corresponds to a community role and a set of privileges +class Group(db.Model): + __tablename__ = 'group' + + # Unique group ID + id = db.Column(db.Integer, primary_key=True) + # Full name, such as "Administrateur" or "Membre d'honneur". + name = db.Column(db.Unicode(50), unique=True) + # The CSS code should not assume any specific layout and typically applies + # to a text node. Use attributes like color, font-style, font-weight, etc. + css = db.Column(db.UnicodeText) + # List of members (lambda delays evaluation) + members = db.relationship('Member', secondary=lambda:GroupMember, + back_populates='groups') + + def __repr__(self): + return f'' + +# Many-to-many relation for users belonging to groups +GroupMember = db.Table('group_member', db.Model.metadata, + db.Column('gid', db.Integer, db.ForeignKey('group.id')), + db.Column('uid', db.Integer, db.ForeignKey('member.id'))) + +# GroupPrivilege: Privilege granted to all users in a group +class GroupPrivilege(db.Model): + __tablename__ = 'group_privilege' + id = db.Column(db.Integer, primary_key=True) + + # Group that is granted the privilege + gid = db.Column(db.Integer, db.ForeignKey('group.id'), index=True) + # Privilege name + priv = db.Column(db.String(Config.PRIVS_MAXLEN)) diff --git a/app/models/users.py b/app/models/users.py index 312880f..d800bb5 100644 --- a/app/models/users.py +++ b/app/models/users.py @@ -2,6 +2,7 @@ from datetime import date, datetime from app import db from flask_login import UserMixin from app.models.contents import Content +from app.models.privs import Group, GroupMember import werkzeug.security import app @@ -60,6 +61,10 @@ class Member(User, db.Model): innovation = db.Column(db.Integer) register_date = db.Column(db.Date, default=date.today) + # Groups and related privileges + groups = db.relationship('Group', secondary=GroupMember, + back_populates='members') + # Personal information, all optional bio = db.Column(db.UnicodeText) signature = db.Column(db.UnicodeText) @@ -196,13 +201,3 @@ class Member(User, db.Model): @app.login.user_loader def load_user(id): return User.query.get(int(id)) - -class Group(db.Model): - __tablename__ = 'group' - - id = db.Column(db.Integer, primary_key=True) - # Full name, such as "Administrateur" or "Membre d'honneur". - name = db.Column(db.Unicode(50), unique=True) - # The CSS code should not assume any specific layout and typically applies - # to a text node. Use attributes like color, font-style, font-weight, etc. - css = db.Column(db.UnicodeText) diff --git a/app/relationship.py b/app/relationship.py deleted file mode 100644 index 7fdd30b..0000000 --- a/app/relationship.py +++ /dev/null @@ -1,15 +0,0 @@ -from app import db - -class Parent(): - __tablename__ = 'user' - id = db.Column(db.Integer, primary_key=True) - type = db.Column(db.String(20)) - __mapper_args__ = { - 'polymorphic_identity': __tablename__, - 'polymorphic_on': type - } - -class Children(Parent): - __tablename__ = 'member' - id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True) - __mapper_args__ = { 'polymorphic_identity': __tablename__ } \ No newline at end of file diff --git a/config.py b/config.py index 82ec0d4..ed978ff 100644 --- a/config.py +++ b/config.py @@ -5,3 +5,6 @@ class Config(object): SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \ 'postgresql+psycopg2://' + os.environ.get('USER') + ':@/pcv5' SQLALCHEMY_TRACK_MODIFICATIONS = False + + # Length allocated to privilege names (slugs) + PRIVS_MAXLEN = 64 diff --git a/migrations/versions/29ca8250bd4a_.py b/migrations/versions/29ca8250bd4a_.py new file mode 100644 index 0000000..d509635 --- /dev/null +++ b/migrations/versions/29ca8250bd4a_.py @@ -0,0 +1,53 @@ +"""empty message + +Revision ID: 29ca8250bd4a +Revises: d2c96bebc596 +Create Date: 2019-02-03 14:45:18.339043 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '29ca8250bd4a' +down_revision = 'd2c96bebc596' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('group_privilege', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('gid', sa.Integer(), nullable=True), + sa.Column('priv', sa.String(length=64), nullable=True), + sa.ForeignKeyConstraint(['gid'], ['group.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_group_privilege_gid'), 'group_privilege', ['gid'], unique=False) + op.create_table('group_user', + sa.Column('gid', sa.Integer(), nullable=True), + sa.Column('uid', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['gid'], ['group.id'], ), + sa.ForeignKeyConstraint(['uid'], ['user.id'], ) + ) + op.create_table('special_privilege', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('uid', sa.Integer(), nullable=True), + sa.Column('priv', sa.String(length=64), nullable=True), + sa.ForeignKeyConstraint(['uid'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_special_privilege_uid'), 'special_privilege', ['uid'], unique=False) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_special_privilege_uid'), table_name='special_privilege') + op.drop_table('special_privilege') + op.drop_table('group_user') + op.drop_index(op.f('ix_group_privilege_gid'), table_name='group_privilege') + op.drop_table('group_privilege') + # ### end Alembic commands ### diff --git a/migrations/versions/8b2cd63804b3_.py b/migrations/versions/8b2cd63804b3_.py new file mode 100644 index 0000000..7d06be5 --- /dev/null +++ b/migrations/versions/8b2cd63804b3_.py @@ -0,0 +1,40 @@ +"""empty message + +Revision ID: 8b2cd63804b3 +Revises: 29ca8250bd4a +Create Date: 2019-02-03 14:54:10.804975 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '8b2cd63804b3' +down_revision = '29ca8250bd4a' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('group_member', + sa.Column('gid', sa.Integer(), nullable=True), + sa.Column('uid', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['gid'], ['group.id'], ), + sa.ForeignKeyConstraint(['uid'], ['member.id'], ) + ) + op.drop_table('group_user') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('group_user', + sa.Column('gid', sa.INTEGER(), autoincrement=False, nullable=True), + sa.Column('uid', sa.INTEGER(), autoincrement=False, nullable=True), + sa.ForeignKeyConstraint(['gid'], ['group.id'], name='group_user_gid_fkey'), + sa.ForeignKeyConstraint(['uid'], ['user.id'], name='group_user_uid_fkey') + ) + op.drop_table('group_member') + # ### end Alembic commands ###