Loading portal/database/exceptions.py 0 → 100644 +7 −0 Original line number Diff line number Diff line from sqlalchemy.exc import SQLAlchemyError class PortalDbError(SQLAlchemyError): """Raised when a forbidden operation on portal's database is attempted. """ No newline at end of file portal/database/mixins.py +4 −2 Original line number Diff line number Diff line Loading @@ -5,6 +5,8 @@ A collection of mixins specifying common behaviour and attributes of database en """ # maybe use server_default, server_onupdate instead of default, onupdate; crashes tests # (https://stackoverflow.com/questions/13370317/sqlalchemy-default-datetime) class EntityBase(object): created_at = db.Column(db.DateTime, default=db.func.now()) updated_at = db.Column(db.DateTime, default=db.func.now(), onupdate=db.func.now()) No newline at end of file created_at = db.Column(db.TIMESTAMP, default=db.func.now()) updated_at = db.Column(db.TIMESTAMP, default=db.func.now(), onupdate=db.func.now()) portal/database/models.py +193 −61 Original line number Diff line number Diff line from portal import db from portal.database.mixins import EntityBase from sqlalchemy.ext.hybrid import hybrid_property import uuid from portal.database.exceptions import PortalDbError from sqlalchemy import event import enum import datetime # uuid as primary key source: # https://stackoverflow.com/questions/36806403/cant-render-element-of-type-class-sqlalchemy-dialects-postgresql-base-uuid def _repr(instance): Loading @@ -8,126 +14,252 @@ def _repr(instance): for key, value in vars(instance).items(): if not key.startswith("_"): result += f"{key}={value} " ''' for attr in dir(instance): if not callable(getattr(instance, attr)) and not (attr.startswith("_")): result += f"{attr}\n" ''' return result class User(db.Model, EntityBase): __tablename__ = 'user' id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Text(length=36), default=lambda: str(uuid.uuid4()), primary_key=True) uco = db.Column(db.Integer, unique=True, nullable=False) email = db.Column(db.String(50), unique=True, nullable=False) xlogin = db.Column(db.String(7), unique=True, nullable=False) username = db.Column(db.String(15), unique=True, nullable=False) name = db.Column(db.String(50)) is_admin = db.Column(db.Boolean, default=False) password = db.Column(db.String(50), default=None) passwordHash = db.Column(db.String(50), default=None) # optional - after password change submissions = db.relationship("Submission", back_populates="user") submissions = db.relationship("Submission", back_populates="user", cascade="all, delete-orphan", passive_deletes=True) @hybrid_property @property def courses(self): return [role.course for role in self.roles] # TODO: use db join result = [] for role in self.roles: if role.course not in result: result.append(role.course) return result def __init__(self, uco, email, xlogin, is_admin=False) -> None: def __init__(self, uco, email, username, is_admin=False) -> None: self.uco = uco self.email = email self.xlogin = xlogin self.username = username self.is_admin = is_admin def __repr__(self): return _repr(self) class Group(db.Model, EntityBase): __tablename__ = 'group' id = db.Column(db.Integer, primary_key=True) course_id = db.Column(db.Integer, db.ForeignKey('course.id')) course = db.relationship("Course", back_populates="groups") users = db.relationship("User", secondary="users_groups") def __init__(self) -> None: pass def __repr__(self): _repr(self) def __eq__(self, other): return self.id == other.id class Course(db.Model, EntityBase): __tablename__ = 'course' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50)) codename = db.Column(db.String(6)) id = db.Column(db.Text(length=36), default=lambda: str(uuid.uuid4()), primary_key=True) name = db.Column(db.String(50), unique=True, nullable=False) codename = db.Column(db.String(15), unique=True, nullable=False) roles = db.relationship("Role", back_populates="course") groups = db.relationship("Group", back_populates="course") projects = db.relationship("Project", back_populates="course") # Info on cascades: http://docs.sqlalchemy.org/en/latest/orm/cascades.html # Cascades here simulate a composition - roles, groups and projects exist only when associated to a course roles = db.relationship("Role", back_populates="course", cascade="all, delete-orphan", passive_deletes=True) groups = db.relationship("Group", back_populates="course", cascade="all, delete-orphan", passive_deletes=True) projects = db.relationship("Project", back_populates="course", cascade="all, delete-orphan", passive_deletes=True) def __init__(self, name, codename) -> None: self.name = name self.codename = codename def __repr__(self): _repr(self) return _repr(self) def __eq__(self, other): return self.id == other.id class ProjectState(enum.Enum): ACTIVE = 1 INACTIVE = 2 ARCHIVED = 3 class Project(db.Model, EntityBase): __tablename__ = 'project' id = db.Column(db.Integer, primary_key=True) course_id = db.Column(db.Integer, db.ForeignKey('course.id')) course = db.relationship("Course", back_populates="projects") id = db.Column(db.Text(length=36), default=lambda: str(uuid.uuid4()), primary_key=True) name = db.Column(db.String(50), nullable=False) test_files_source = db.Column(db.String(100), nullable=False) # a git repository URL file_whitelist = db.Column(db.Text) pre_submit_script = db.Column(db.Text) post_submit_script = db.Column(db.Text) submission_parameters = db.Column(db.Text) submission_scheduler_config = db.Column(db.Text) submissions_allowed_from = db.Column(db.TIMESTAMP(timezone=True)) submissions_allowed_to = db.Column(db.TIMESTAMP(timezone=True)) archive_from = db.Column(db.TIMESTAMP(timezone=True)) # TODO: validate date values: http://docs.sqlalchemy.org/en/rel_0_9/orm/mapped_attributes.html#simple-validators course_id = db.Column(db.Integer, db.ForeignKey('course.id'), nullable=False) course = db.relationship("Course", back_populates="projects", uselist=False) __table_args__ = ( db.UniqueConstraint('course_id', 'name', name='course_unique_name'), ) # check timestamp comparisons def state(self, timestamp=datetime.datetime.now()) -> ProjectState: if self.submissions_allowed_from < timestamp < self.submissions_allowed_to: return ProjectState.ACTIVE elif timestamp > self.archive_from: return ProjectState.ARCHIVED else: return ProjectState.INACTIVE def __init__(self, course, name, test_files_source) -> None: self.course = course self.name = name self.test_files_source = test_files_source # TODO configuration saving - kontr, submission def __repr__(self): _repr(self) return _repr(self) def __eq__(self, other): return self.id == other.id class Role(db.Model, EntityBase): __tablename__ = 'role' id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Text(length=36), default=lambda: str(uuid.uuid4()), primary_key=True) name = db.Column(db.String(50), nullable=False) description = db.Column(db.Text) users = db.relationship("User", secondary="users_roles") course_id = db.Column(db.Integer, db.ForeignKey('course.id')) course = db.relationship("Course", back_populates="roles") permissions = {} # how to represent these course_id = db.Column(db.Integer, db.ForeignKey('course.id'), nullable=False) course = db.relationship("Course", back_populates="roles", uselist=False) permissions = {} # TODO __table_args__ = ( db.UniqueConstraint('course_id', 'name', name='course_unique_name'), ) def __init__(self, course): def __init__(self, course, name): self.course = course self.name = name def __repr__(self): _repr(self) return _repr(self) def __eq__(self, other): return self.id == other.id class Group(db.Model, EntityBase): __tablename__ = 'group' id = db.Column(db.Text(length=36), default=lambda: str(uuid.uuid4()), primary_key=True) name = db.Column(db.String(20), nullable=False) course_id = db.Column(db.Integer, db.ForeignKey('course.id'), nullable=False) course = db.relationship("Course", back_populates="groups", uselist=False) users = db.relationship("User", secondary="users_groups") __table_args__ = ( db.UniqueConstraint('course_id', 'name', name='course_unique_name'), ) def __init__(self, course, name) -> None: self.course = course self.name = name def __repr__(self): return _repr(self) def __eq__(self, other): return self.id == other.id class SubmissionState(enum.Enum): CREATED = 1 READY = 2 IN_PROGRESS = 3 FINISHED = 4 class Submission(db.Model, EntityBase): __tablename__ = 'submission' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) user = db.relationship("User", back_populates="submissions") project_id = db.Column(db.Integer, db.ForeignKey('project.id')) id = db.Column(db.Text(length=36), default=lambda: str(uuid.uuid4()), primary_key=True) processing_scheduled_for = db.Column(db.TIMESTAMP(timezone=True), nullable=False) parameters = db.Column(db.Text, nullable=False) state = db.Column(db.Enum(SubmissionState, name='SubmissionState'), nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) user = db.relationship("User", back_populates="submissions", uselist=False) project_id = db.Column(db.Integer, db.ForeignKey('project.id'), nullable=False) project = db.relationship("Project", uselist=False) review = [] result = [] review = db.relationship("Review", back_populates="submission", cascade="all, delete-orphan", passive_deletes=True, uselist=False) def __init__(self): pass def __init__(self, user, project, processing_scheduled_for, parameters, review=None): self.user = user self.project = project self.review = review self.processing_scheduled_for = processing_scheduled_for self.parameters = parameters self.state = SubmissionState.CREATED def __repr__(self): _repr(self) return _repr(self) def __eq__(self, other): return self.id == other.id class Review(db.Model, EntityBase): # might not be in database, but rather in storage class Review(db.Model, EntityBase): __tablename__ = 'review' id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Text(length=36), default=lambda: str(uuid.uuid4()), primary_key=True) submission_id = db.Column(db.Integer, db.ForeignKey('submission.id'), nullable=False) submission = db.relationship("Submission", uselist=False) reviewItems = db.relationship("ReviewItem", back_populates="review", cascade="all, delete-orphan", passive_deletes=True) def __init__(self): pass def __init__(self, submission): self.submission = submission def __repr__(self): _repr(self) return _repr(self) def __eq__(self, other): return self.id == other.id class ReviewItem(db.Model, EntityBase): __tablename__ = 'reviewItem' id = db.Column(db.Text(length=36), default=lambda: str(uuid.uuid4()), primary_key=True) review_id = db.Column(db.Integer, db.ForeignKey('review.id'), nullable=False) review = db.relationship("Review", uselist=False) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) user = db.relationship("User", uselist=False) # the review item's author content = db.Column(db.Text) file = db.Column(db.String(100), nullable=False) line = db.Column(db.Integer, nullable=False) def __init__(self, user, review, file, line): self.review = review self.user = user self.file = file self.line = line def __repr__(self): return _repr(self) def __eq__(self, other): return self.id == other.id @event.listens_for(Submission.user_id, 'set', named=True) def receive_set(**kw): """listen for the 'set' event""" submission = kw['target'] value = kw['value'] if submission.user_id is not None and submission.user_id != value: raise PortalDbError() # Associations # Associations - must come after affected class definitions users_groups = db.Table("users_groups", db.Model.metadata, db.Column("user_id", db.Integer, db.ForeignKey('user.id')), db.Column("group_id", db.Integer, db.ForeignKey('group.id'))) Loading @@ -142,4 +274,4 @@ projects_groups = db.Table("projects_groups", db.Model.metadata, User.roles = db.relationship("Role", secondary="users_roles") User.groups = db.relationship("Group", secondary="users_groups") Project.groups = db.relationship("Group", secondary="projects_groups") # should create a one-way relationship Project.groups = db.relationship("Group", secondary="projects_groups") run.py +3 −9 Original line number Diff line number Diff line Loading @@ -7,15 +7,6 @@ def init_db(app): db.drop_all(app=app) db.create_all(app=app) ''' def init_data(): with app.app_context(): from portal.database.models import User user = User(uco=445, email="foo", xlogin="xbar") db.session.add(user) db.session.commit() print(User.query.all()[0].__repr__()) ''' if __name__ == '__main__': app = create_app() Loading @@ -31,6 +22,9 @@ if __name__ == '__main__': # flask-wtf for auth # https://scotch.io/tutorials/build-a-crud-web-app-with-python-and-flask-part-one logging: (email sending!): http://flask.pocoo.org/docs/dev/logging/ https://docs.python.org/3/howto/logging-cookbook.html#formatting-styles REST & js: https://auth0.com/blog/developing-restful-apis-with-python-and-flask/ Loading sample_data/data_init.py +9 −9 Original line number Diff line number Diff line Loading @@ -13,8 +13,8 @@ def init_data(app, db): def create_users(db): db.session.add(User(uco=445, email="foo", xlogin="xfoo")) db.session.add(User(uco=123, email="bar", xlogin="xbar")) db.session.add(User(uco=445, email="foo", username="xfoo")) db.session.add(User(uco=123, email="bar", username="xbar")) db.session.commit() Loading @@ -25,20 +25,20 @@ def create_courses(db): def create_roles(db): db.session.add(Role(course=Course.query.all()[0])) db.session.add(Role(course=Course.query.all()[1])) db.session.add(Role(name='student', course=Course.query.all()[0])) db.session.add(Role(name='teacher', course=Course.query.all()[1])) db.session.commit() def create_projects(db): db.session.add(Project()) db.session.add(Project()) db.session.add(Project(course=Course.query.all()[0], name="p1", test_files_source="repo")) db.session.add(Project(course=Course.query.all()[1], name="p2", test_files_source="repo2")) db.session.commit() def create_groups(db): db.session.add(Group()) db.session.add(Group()) db.session.add(Group(course=Course.query.all()[0], name="g1")) db.session.add(Group(course=Course.query.all()[1], name="g2")) db.session.commit() Loading @@ -47,7 +47,7 @@ def create_submissions(db): def create_relationships(db): user1 = User.query.filter_by(xlogin="xfoo").first() user1 = User.query.filter_by(username="xfoo").first() user1.roles.append(Role.query.all()[0]) user1.groups.append(Group.query.all()[0]) db.session.commit() Loading Loading
portal/database/exceptions.py 0 → 100644 +7 −0 Original line number Diff line number Diff line from sqlalchemy.exc import SQLAlchemyError class PortalDbError(SQLAlchemyError): """Raised when a forbidden operation on portal's database is attempted. """ No newline at end of file
portal/database/mixins.py +4 −2 Original line number Diff line number Diff line Loading @@ -5,6 +5,8 @@ A collection of mixins specifying common behaviour and attributes of database en """ # maybe use server_default, server_onupdate instead of default, onupdate; crashes tests # (https://stackoverflow.com/questions/13370317/sqlalchemy-default-datetime) class EntityBase(object): created_at = db.Column(db.DateTime, default=db.func.now()) updated_at = db.Column(db.DateTime, default=db.func.now(), onupdate=db.func.now()) No newline at end of file created_at = db.Column(db.TIMESTAMP, default=db.func.now()) updated_at = db.Column(db.TIMESTAMP, default=db.func.now(), onupdate=db.func.now())
portal/database/models.py +193 −61 Original line number Diff line number Diff line from portal import db from portal.database.mixins import EntityBase from sqlalchemy.ext.hybrid import hybrid_property import uuid from portal.database.exceptions import PortalDbError from sqlalchemy import event import enum import datetime # uuid as primary key source: # https://stackoverflow.com/questions/36806403/cant-render-element-of-type-class-sqlalchemy-dialects-postgresql-base-uuid def _repr(instance): Loading @@ -8,126 +14,252 @@ def _repr(instance): for key, value in vars(instance).items(): if not key.startswith("_"): result += f"{key}={value} " ''' for attr in dir(instance): if not callable(getattr(instance, attr)) and not (attr.startswith("_")): result += f"{attr}\n" ''' return result class User(db.Model, EntityBase): __tablename__ = 'user' id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Text(length=36), default=lambda: str(uuid.uuid4()), primary_key=True) uco = db.Column(db.Integer, unique=True, nullable=False) email = db.Column(db.String(50), unique=True, nullable=False) xlogin = db.Column(db.String(7), unique=True, nullable=False) username = db.Column(db.String(15), unique=True, nullable=False) name = db.Column(db.String(50)) is_admin = db.Column(db.Boolean, default=False) password = db.Column(db.String(50), default=None) passwordHash = db.Column(db.String(50), default=None) # optional - after password change submissions = db.relationship("Submission", back_populates="user") submissions = db.relationship("Submission", back_populates="user", cascade="all, delete-orphan", passive_deletes=True) @hybrid_property @property def courses(self): return [role.course for role in self.roles] # TODO: use db join result = [] for role in self.roles: if role.course not in result: result.append(role.course) return result def __init__(self, uco, email, xlogin, is_admin=False) -> None: def __init__(self, uco, email, username, is_admin=False) -> None: self.uco = uco self.email = email self.xlogin = xlogin self.username = username self.is_admin = is_admin def __repr__(self): return _repr(self) class Group(db.Model, EntityBase): __tablename__ = 'group' id = db.Column(db.Integer, primary_key=True) course_id = db.Column(db.Integer, db.ForeignKey('course.id')) course = db.relationship("Course", back_populates="groups") users = db.relationship("User", secondary="users_groups") def __init__(self) -> None: pass def __repr__(self): _repr(self) def __eq__(self, other): return self.id == other.id class Course(db.Model, EntityBase): __tablename__ = 'course' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50)) codename = db.Column(db.String(6)) id = db.Column(db.Text(length=36), default=lambda: str(uuid.uuid4()), primary_key=True) name = db.Column(db.String(50), unique=True, nullable=False) codename = db.Column(db.String(15), unique=True, nullable=False) roles = db.relationship("Role", back_populates="course") groups = db.relationship("Group", back_populates="course") projects = db.relationship("Project", back_populates="course") # Info on cascades: http://docs.sqlalchemy.org/en/latest/orm/cascades.html # Cascades here simulate a composition - roles, groups and projects exist only when associated to a course roles = db.relationship("Role", back_populates="course", cascade="all, delete-orphan", passive_deletes=True) groups = db.relationship("Group", back_populates="course", cascade="all, delete-orphan", passive_deletes=True) projects = db.relationship("Project", back_populates="course", cascade="all, delete-orphan", passive_deletes=True) def __init__(self, name, codename) -> None: self.name = name self.codename = codename def __repr__(self): _repr(self) return _repr(self) def __eq__(self, other): return self.id == other.id class ProjectState(enum.Enum): ACTIVE = 1 INACTIVE = 2 ARCHIVED = 3 class Project(db.Model, EntityBase): __tablename__ = 'project' id = db.Column(db.Integer, primary_key=True) course_id = db.Column(db.Integer, db.ForeignKey('course.id')) course = db.relationship("Course", back_populates="projects") id = db.Column(db.Text(length=36), default=lambda: str(uuid.uuid4()), primary_key=True) name = db.Column(db.String(50), nullable=False) test_files_source = db.Column(db.String(100), nullable=False) # a git repository URL file_whitelist = db.Column(db.Text) pre_submit_script = db.Column(db.Text) post_submit_script = db.Column(db.Text) submission_parameters = db.Column(db.Text) submission_scheduler_config = db.Column(db.Text) submissions_allowed_from = db.Column(db.TIMESTAMP(timezone=True)) submissions_allowed_to = db.Column(db.TIMESTAMP(timezone=True)) archive_from = db.Column(db.TIMESTAMP(timezone=True)) # TODO: validate date values: http://docs.sqlalchemy.org/en/rel_0_9/orm/mapped_attributes.html#simple-validators course_id = db.Column(db.Integer, db.ForeignKey('course.id'), nullable=False) course = db.relationship("Course", back_populates="projects", uselist=False) __table_args__ = ( db.UniqueConstraint('course_id', 'name', name='course_unique_name'), ) # check timestamp comparisons def state(self, timestamp=datetime.datetime.now()) -> ProjectState: if self.submissions_allowed_from < timestamp < self.submissions_allowed_to: return ProjectState.ACTIVE elif timestamp > self.archive_from: return ProjectState.ARCHIVED else: return ProjectState.INACTIVE def __init__(self, course, name, test_files_source) -> None: self.course = course self.name = name self.test_files_source = test_files_source # TODO configuration saving - kontr, submission def __repr__(self): _repr(self) return _repr(self) def __eq__(self, other): return self.id == other.id class Role(db.Model, EntityBase): __tablename__ = 'role' id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Text(length=36), default=lambda: str(uuid.uuid4()), primary_key=True) name = db.Column(db.String(50), nullable=False) description = db.Column(db.Text) users = db.relationship("User", secondary="users_roles") course_id = db.Column(db.Integer, db.ForeignKey('course.id')) course = db.relationship("Course", back_populates="roles") permissions = {} # how to represent these course_id = db.Column(db.Integer, db.ForeignKey('course.id'), nullable=False) course = db.relationship("Course", back_populates="roles", uselist=False) permissions = {} # TODO __table_args__ = ( db.UniqueConstraint('course_id', 'name', name='course_unique_name'), ) def __init__(self, course): def __init__(self, course, name): self.course = course self.name = name def __repr__(self): _repr(self) return _repr(self) def __eq__(self, other): return self.id == other.id class Group(db.Model, EntityBase): __tablename__ = 'group' id = db.Column(db.Text(length=36), default=lambda: str(uuid.uuid4()), primary_key=True) name = db.Column(db.String(20), nullable=False) course_id = db.Column(db.Integer, db.ForeignKey('course.id'), nullable=False) course = db.relationship("Course", back_populates="groups", uselist=False) users = db.relationship("User", secondary="users_groups") __table_args__ = ( db.UniqueConstraint('course_id', 'name', name='course_unique_name'), ) def __init__(self, course, name) -> None: self.course = course self.name = name def __repr__(self): return _repr(self) def __eq__(self, other): return self.id == other.id class SubmissionState(enum.Enum): CREATED = 1 READY = 2 IN_PROGRESS = 3 FINISHED = 4 class Submission(db.Model, EntityBase): __tablename__ = 'submission' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) user = db.relationship("User", back_populates="submissions") project_id = db.Column(db.Integer, db.ForeignKey('project.id')) id = db.Column(db.Text(length=36), default=lambda: str(uuid.uuid4()), primary_key=True) processing_scheduled_for = db.Column(db.TIMESTAMP(timezone=True), nullable=False) parameters = db.Column(db.Text, nullable=False) state = db.Column(db.Enum(SubmissionState, name='SubmissionState'), nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) user = db.relationship("User", back_populates="submissions", uselist=False) project_id = db.Column(db.Integer, db.ForeignKey('project.id'), nullable=False) project = db.relationship("Project", uselist=False) review = [] result = [] review = db.relationship("Review", back_populates="submission", cascade="all, delete-orphan", passive_deletes=True, uselist=False) def __init__(self): pass def __init__(self, user, project, processing_scheduled_for, parameters, review=None): self.user = user self.project = project self.review = review self.processing_scheduled_for = processing_scheduled_for self.parameters = parameters self.state = SubmissionState.CREATED def __repr__(self): _repr(self) return _repr(self) def __eq__(self, other): return self.id == other.id class Review(db.Model, EntityBase): # might not be in database, but rather in storage class Review(db.Model, EntityBase): __tablename__ = 'review' id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Text(length=36), default=lambda: str(uuid.uuid4()), primary_key=True) submission_id = db.Column(db.Integer, db.ForeignKey('submission.id'), nullable=False) submission = db.relationship("Submission", uselist=False) reviewItems = db.relationship("ReviewItem", back_populates="review", cascade="all, delete-orphan", passive_deletes=True) def __init__(self): pass def __init__(self, submission): self.submission = submission def __repr__(self): _repr(self) return _repr(self) def __eq__(self, other): return self.id == other.id class ReviewItem(db.Model, EntityBase): __tablename__ = 'reviewItem' id = db.Column(db.Text(length=36), default=lambda: str(uuid.uuid4()), primary_key=True) review_id = db.Column(db.Integer, db.ForeignKey('review.id'), nullable=False) review = db.relationship("Review", uselist=False) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) user = db.relationship("User", uselist=False) # the review item's author content = db.Column(db.Text) file = db.Column(db.String(100), nullable=False) line = db.Column(db.Integer, nullable=False) def __init__(self, user, review, file, line): self.review = review self.user = user self.file = file self.line = line def __repr__(self): return _repr(self) def __eq__(self, other): return self.id == other.id @event.listens_for(Submission.user_id, 'set', named=True) def receive_set(**kw): """listen for the 'set' event""" submission = kw['target'] value = kw['value'] if submission.user_id is not None and submission.user_id != value: raise PortalDbError() # Associations # Associations - must come after affected class definitions users_groups = db.Table("users_groups", db.Model.metadata, db.Column("user_id", db.Integer, db.ForeignKey('user.id')), db.Column("group_id", db.Integer, db.ForeignKey('group.id'))) Loading @@ -142,4 +274,4 @@ projects_groups = db.Table("projects_groups", db.Model.metadata, User.roles = db.relationship("Role", secondary="users_roles") User.groups = db.relationship("Group", secondary="users_groups") Project.groups = db.relationship("Group", secondary="projects_groups") # should create a one-way relationship Project.groups = db.relationship("Group", secondary="projects_groups")
run.py +3 −9 Original line number Diff line number Diff line Loading @@ -7,15 +7,6 @@ def init_db(app): db.drop_all(app=app) db.create_all(app=app) ''' def init_data(): with app.app_context(): from portal.database.models import User user = User(uco=445, email="foo", xlogin="xbar") db.session.add(user) db.session.commit() print(User.query.all()[0].__repr__()) ''' if __name__ == '__main__': app = create_app() Loading @@ -31,6 +22,9 @@ if __name__ == '__main__': # flask-wtf for auth # https://scotch.io/tutorials/build-a-crud-web-app-with-python-and-flask-part-one logging: (email sending!): http://flask.pocoo.org/docs/dev/logging/ https://docs.python.org/3/howto/logging-cookbook.html#formatting-styles REST & js: https://auth0.com/blog/developing-restful-apis-with-python-and-flask/ Loading
sample_data/data_init.py +9 −9 Original line number Diff line number Diff line Loading @@ -13,8 +13,8 @@ def init_data(app, db): def create_users(db): db.session.add(User(uco=445, email="foo", xlogin="xfoo")) db.session.add(User(uco=123, email="bar", xlogin="xbar")) db.session.add(User(uco=445, email="foo", username="xfoo")) db.session.add(User(uco=123, email="bar", username="xbar")) db.session.commit() Loading @@ -25,20 +25,20 @@ def create_courses(db): def create_roles(db): db.session.add(Role(course=Course.query.all()[0])) db.session.add(Role(course=Course.query.all()[1])) db.session.add(Role(name='student', course=Course.query.all()[0])) db.session.add(Role(name='teacher', course=Course.query.all()[1])) db.session.commit() def create_projects(db): db.session.add(Project()) db.session.add(Project()) db.session.add(Project(course=Course.query.all()[0], name="p1", test_files_source="repo")) db.session.add(Project(course=Course.query.all()[1], name="p2", test_files_source="repo2")) db.session.commit() def create_groups(db): db.session.add(Group()) db.session.add(Group()) db.session.add(Group(course=Course.query.all()[0], name="g1")) db.session.add(Group(course=Course.query.all()[1], name="g2")) db.session.commit() Loading @@ -47,7 +47,7 @@ def create_submissions(db): def create_relationships(db): user1 = User.query.filter_by(xlogin="xfoo").first() user1 = User.query.filter_by(username="xfoo").first() user1.roles.append(Role.query.all()[0]) user1.groups.append(Group.query.all()[0]) db.session.commit() Loading