diff --git a/.gitignore b/.gitignore index 80704f4378..fe00e5d8b1 100755 --- a/.gitignore +++ b/.gitignore @@ -80,3 +80,4 @@ database.database database.db diagram.png __pycache__/ +migrations/ diff --git a/src/api/admin.py b/src/api/admin.py index 3eecb64140..423ac019bf 100644 --- a/src/api/admin.py +++ b/src/api/admin.py @@ -1,9 +1,37 @@ import os from flask_admin import Admin -from .models import db, User +from .models import db, User, Project, Task, Comment, Role, Project_Member, Tags from flask_admin.contrib.sqla import ModelView +class UserModelView(ModelView): + column_auto_select_related = True + column_list = ['id', 'full_name', 'email', 'password', 'phone', 'country', 'created_at', 'profile_picture_url', 'random_profile_color', 'time_zone', 'is_active', 'admin_of', 'member_of', 'author_of_task', 'roles', 'author_of_comment'] + +class ProjectModelView(ModelView): + column_auto_select_related = True + column_list = ['id', 'title', 'description', 'created_at', 'proyect_picture_url', 'due_date', 'status', 'admin_of', 'admin', 'members', 'tasks', 'roles'] + +class ProjectMemberModelView(ModelView): + column_auto_select_related = True + column_list=['id', 'member_id', 'project_id', 'member', 'project'] + +class TaskModelView(ModelView): + column_auto_select_related = True + column_list=['id', 'title', 'description', 'created_at', 'status', 'author_id', 'task_author', 'project_id', 'project', 'comments'] + +class CommentModelView(ModelView): + column_auto_select_related = True + column_list=['id', 'title', 'description', 'created_at', 'task_id', 'task', 'author_id', 'comment_author',] + +class RoleModelView(ModelView): + column_auto_select_related = True + column_list=['id', 'status', 'user_id', 'user', 'project_id', 'project'] + +class TagsModelView(ModelView): + column_auto_select_related = True + column_list=['id', 'tag', 'task_id', 'task'] + def setup_admin(app): app.secret_key = os.environ.get('FLASK_APP_KEY', 'sample key') app.config['FLASK_ADMIN_SWATCH'] = 'cerulean' @@ -11,7 +39,13 @@ def setup_admin(app): # Add your models here, for example this is how we add a the User model to the admin - admin.add_view(ModelView(User, db.session)) + admin.add_view(UserModelView(User, db.session)) + admin.add_view(ProjectModelView(Project, db.session)) + admin.add_view(TaskModelView(Task, db.session)) + admin.add_view(CommentModelView(Comment, db.session)) + admin.add_view(RoleModelView(Role, db.session)) + admin.add_view(ProjectMemberModelView(Project_Member, db.session)) + admin.add_view(TagsModelView(Tags, db.session)) # You can duplicate that line to add mew models # admin.add_view(ModelView(YourModelName, db.session)) \ No newline at end of file diff --git a/src/api/models.py b/src/api/models.py index da515f6a1a..750d4818ca 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -1,19 +1,127 @@ from flask_sqlalchemy import SQLAlchemy -from sqlalchemy import String, Boolean -from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy import String, Boolean, Integer, DateTime, Enum, ForeignKey +from sqlalchemy.orm import Mapped, mapped_column, relationship +import enum db = SQLAlchemy() +class ProjectStatus(enum.Enum): + yet_to_start = "yet to start" + in_progress = "in progress" + done = "done" + dismissed = "dismissed" + +class TaskStatus(enum.Enum): + in_progress = "in progress" + delegated = "delegated" + urgent = "urgent" + done = "done" + +class RoleType(enum.Enum): + admin = "admin" + member = "member" + class User(db.Model): + __tablename__ = 'user' id: Mapped[int] = mapped_column(primary_key=True) + full_name: Mapped[str] = mapped_column(String(120), nullable=False) email: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) password: Mapped[str] = mapped_column(nullable=False) + phone: Mapped[int] = mapped_column(Integer, nullable=True) + country: Mapped[str] = mapped_column(String(120), nullable=False) + created_at: Mapped[DateTime] = mapped_column(DateTime, nullable=False) + profile_picture_url: Mapped[str] = mapped_column(String, unique=True, nullable=True) + random_profile_color: Mapped[int] = mapped_column(Integer, nullable=True) + time_zone: Mapped[str] = mapped_column(String(120), nullable=True) is_active: Mapped[bool] = mapped_column(Boolean(), nullable=False) + admin_of: Mapped[list['Project']]=relationship( + back_populates='admin', cascade='all, delete-orphan') + member_of: Mapped[list['Project_Member']] = relationship( + back_populates='member', cascade='all, delete-orphan') + author_of_task: Mapped[list['Task']] = relationship( + back_populates='task_author', cascade='all, delete-orphan') + roles: Mapped[list['Role']] = relationship( + back_populates='user', cascade='all, delete-orphan') + author_of_comment: Mapped[list['Comment']] = relationship( + back_populates='comment_author', cascade='all, delete-orphan') + def __str__(self): + return f'User {self.full_name}' + +class Project(db.Model): + __tablename__ = 'project' + id: Mapped[int] = mapped_column(primary_key=True) + title: Mapped[str] = mapped_column(String(120), nullable=False) + description: Mapped[str] = mapped_column(String, nullable=True) + created_at: Mapped[DateTime] = mapped_column(DateTime, nullable=False) + proyect_picture_url: Mapped[str] = mapped_column(String, unique=True, nullable=True) + due_date: Mapped[DateTime] = mapped_column(DateTime, nullable=False) + status: Mapped[ProjectStatus] = mapped_column(Enum(ProjectStatus), nullable=False, default=ProjectStatus.yet_to_start) + admin_id: Mapped[int]= mapped_column(ForeignKey('user.id')) + admin: Mapped[User]=relationship(back_populates='admin_of') + members: Mapped[list['Project_Member']] = relationship( + back_populates='project', cascade='all, delete-orphan') + tasks: Mapped[list['Task']] = relationship( + back_populates='project', cascade='all, delete-orphan') + roles: Mapped[list['Role']] = relationship( + back_populates='project', cascade='all, delete-orphan') + def __str__(self): + return f'Project {self.title}' + +class Project_Member(db.Model): + __tablename__ = 'project_member' + id: Mapped[int] = mapped_column(primary_key=True) + member_id: Mapped[int] = mapped_column(ForeignKey('user.id')) + project_id: Mapped[int] = mapped_column(ForeignKey('project.id')) + member: Mapped[User] = relationship(back_populates='member_of') + project: Mapped[Project] = relationship(back_populates='members') + def __str__(self): + return f'Project Member {self.member.full_name} in {self.project.title}' +class Task(db.Model): + __tablename__= 'task' + id: Mapped[int] = mapped_column(primary_key=True) + title: Mapped[str] = mapped_column(String(120), nullable=False) + description: Mapped[str] = mapped_column(String, nullable=True) + created_at: Mapped[DateTime] = mapped_column(DateTime, nullable=False) + status: Mapped[TaskStatus] = mapped_column(Enum(TaskStatus), nullable=False, default=ProjectStatus.in_progress) + author_id: Mapped[int] = mapped_column(ForeignKey('user.id')) + task_author: Mapped[User] = relationship(back_populates='author_of_task') + project_id: Mapped[int] = mapped_column(ForeignKey('project.id')) + project: Mapped[Project] = relationship(back_populates='tasks') + comments: Mapped[list['Comment']] = relationship( + back_populates='task', cascade='all, delete-orphan') + tags: Mapped[list['Tags']] = relationship( + back_populates='task', cascade='all, delete-orphan') + def __str__(self): + return f'Task {self.title} in Project {self.project.title}' + +class Comment(db.Model): + __tablename__='comment' + id: Mapped[int] = mapped_column(primary_key=True) + title: Mapped[str] = mapped_column(String(120), nullable=False) + description: Mapped[str] = mapped_column(String, nullable=False) + created_at: Mapped[DateTime] = mapped_column(DateTime, nullable=False) + task_id: Mapped[int] = mapped_column(ForeignKey('task.id')) + task: Mapped[Task] = relationship(back_populates='comments') + author_id: Mapped[int] = mapped_column(ForeignKey('user.id')) + comment_author: Mapped[User] = relationship(back_populates='author_of_comment') + def __str__(self): + return f'Comment {self.title}: {self.description} on Task {self.task.title} by {self.comment_author.full_name}' - def serialize(self): - return { - "id": self.id, - "email": self.email, - # do not serialize the password, its a security breach - } \ No newline at end of file +class Role(db.Model): + __tablename__='role' + id: Mapped[int] = mapped_column(primary_key=True) + status: Mapped[RoleType] = mapped_column(Enum(RoleType), nullable=False) + user_id: Mapped[int] = mapped_column(ForeignKey('user.id')) + user: Mapped[User] = relationship(back_populates='roles') + project_id: Mapped[int] = mapped_column(ForeignKey('project.id')) + project: Mapped[Project] = relationship(back_populates='roles') + def __str__(self): + return f'Role {self.status} for User {self.user.full_name} in Project {self.project.title}' + +class Tags(db.Model): + __tablename__='tags' + id: Mapped[int] = mapped_column(primary_key=True) + tag: Mapped[str] = mapped_column(String(120), nullable=False) + task_id: Mapped[int] = mapped_column(ForeignKey('task.id')) + task: Mapped[Task] = relationship(back_populates='tags')