feat: create and delete assignment activities from UI

This commit is contained in:
swve 2024-07-12 11:54:33 +02:00
parent 04c05e4f9a
commit 10e9be1d33
14 changed files with 358 additions and 14 deletions

View file

@ -1,7 +1,7 @@
import importlib
from logging.config import fileConfig
import os
import alembic_postgresql_enum
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from sqlmodel import SQLModel

View file

@ -0,0 +1,41 @@
"""Enum updates
Revision ID: 6295e05ff7d0
Revises: df2981bf24dd
Create Date: 2024-07-11 20:46:26.582170
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import sqlmodel
from alembic_postgresql_enum import TableReference # type: ignore
# revision identifiers, used by Alembic.
revision: str = '6295e05ff7d0'
down_revision: Union[str, None] = 'df2981bf24dd'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.sync_enum_values('public', 'activitytypeenum', ['TYPE_VIDEO', 'TYPE_DOCUMENT', 'TYPE_DYNAMIC', 'TYPE_ASSIGNMENT', 'TYPE_CUSTOM'],
[TableReference(table_schema='public', table_name='activity', column_name='activity_type')],
enum_values_to_rename=[])
op.sync_enum_values('public', 'activitysubtypeenum', ['SUBTYPE_DYNAMIC_PAGE', 'SUBTYPE_VIDEO_YOUTUBE', 'SUBTYPE_VIDEO_HOSTED', 'SUBTYPE_DOCUMENT_PDF', 'SUBTYPE_DOCUMENT_DOC', 'SUBTYPE_ASSIGNMENT_ANY', 'SUBTYPE_CUSTOM'],
[TableReference(table_schema='public', table_name='activity', column_name='activity_sub_type')],
enum_values_to_rename=[])
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.sync_enum_values('public', 'activitysubtypeenum', ['SUBTYPE_DYNAMIC_PAGE', 'SUBTYPE_VIDEO_YOUTUBE', 'SUBTYPE_VIDEO_HOSTED', 'SUBTYPE_DOCUMENT_PDF', 'SUBTYPE_DOCUMENT_DOC', 'SUBTYPE_ASSESSMENT_QUIZ', 'SUBTYPE_CUSTOM'],
[TableReference(table_schema='public', table_name='activity', column_name='activity_sub_type')],
enum_values_to_rename=[])
op.sync_enum_values('public', 'activitytypeenum', ['TYPE_VIDEO', 'TYPE_DOCUMENT', 'TYPE_DYNAMIC', 'TYPE_ASSESSMENT', 'TYPE_CUSTOM'],
[TableReference(table_schema='public', table_name='activity', column_name='activity_type')],
enum_values_to_rename=[])
# ### end Alembic commands ###

View file

@ -23,6 +23,9 @@ depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('activity', sa.Column('published', sa.Boolean(), nullable=False, server_default=sa.true()))
# If you need to rename columns instead of dropping them, use the rename_column command
# For example, if we are changing the name 'published_version' to 'published', we would use:
# op.alter_column('activity', 'published_version', new_column_name='published', existing_type=sa.Boolean())
op.drop_column('activity', 'published_version')
op.drop_column('activity', 'version')
@ -32,7 +35,6 @@ def upgrade() -> None:
op.create_foreign_key('trail_org_id_fkey', 'trail', 'organization', ['org_id'], ['id'], ondelete='CASCADE')
op.drop_constraint('trail_user_id_fkey', 'trail', type_='foreignkey')
op.create_foreign_key('trail_user_id_fkey', 'trail', 'user', ['user_id'], ['id'], ondelete='CASCADE')
# ### end Alembic commands ###
@ -45,7 +47,8 @@ def downgrade() -> None:
op.add_column('assignmentusersubmission', sa.Column('assignment_user_uuid', sa.VARCHAR(), autoincrement=False, nullable=False))
op.add_column('activity', sa.Column('version', sa.INTEGER(), autoincrement=False, nullable=False , server_default=sa.text('1')))
op.add_column('activity', sa.Column('published_version', sa.INTEGER(), autoincrement=False, nullable=False , server_default=sa.text('1')) )
op.add_column('activity', sa.Column('version', sa.INTEGER(), autoincrement=False, nullable=False, server_default=sa.text('1')))
op.add_column('activity', sa.Column('published_version', sa.INTEGER(), autoincrement=False, nullable=False, server_default=sa.text('1')))
op.drop_column('activity', 'published')
# ### end Alembic commands ###

45
apps/api/poetry.lock generated
View file

@ -128,6 +128,21 @@ typing-extensions = ">=4"
[package.extras]
tz = ["backports.zoneinfo"]
[[package]]
name = "alembic-postgresql-enum"
version = "1.2.0"
description = "Alembic autogenerate support for creation, alteration and deletion of enums"
optional = false
python-versions = "<4.0,>=3.7"
files = [
{file = "alembic_postgresql_enum-1.2.0-py3-none-any.whl", hash = "sha256:bd156e882a10c680fc88ebad25cfe78ccf9f826dec89670f8aeb28e5359e502b"},
{file = "alembic_postgresql_enum-1.2.0.tar.gz", hash = "sha256:971bd3a4c35ea38869bb5e263ea79e5b4a9c4a02f174a3dd7ddcb29d41260cba"},
]
[package.dependencies]
alembic = ">=1.7"
SQLAlchemy = ">=1.4"
[[package]]
name = "anyio"
version = "4.4.0"
@ -3018,6 +3033,34 @@ postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"]
pymysql = ["pymysql"]
sqlcipher = ["sqlcipher3_binary"]
[[package]]
name = "sqlalchemy-utils"
version = "0.41.2"
description = "Various utility functions for SQLAlchemy."
optional = false
python-versions = ">=3.7"
files = [
{file = "SQLAlchemy-Utils-0.41.2.tar.gz", hash = "sha256:bc599c8c3b3319e53ce6c5c3c471120bd325d0071fb6f38a10e924e3d07b9990"},
{file = "SQLAlchemy_Utils-0.41.2-py3-none-any.whl", hash = "sha256:85cf3842da2bf060760f955f8467b87983fb2e30f1764fd0e24a48307dc8ec6e"},
]
[package.dependencies]
SQLAlchemy = ">=1.3"
[package.extras]
arrow = ["arrow (>=0.3.4)"]
babel = ["Babel (>=1.3)"]
color = ["colour (>=0.0.4)"]
encrypted = ["cryptography (>=0.6)"]
intervals = ["intervals (>=0.7.1)"]
password = ["passlib (>=1.6,<2.0)"]
pendulum = ["pendulum (>=2.0.5)"]
phone = ["phonenumbers (>=5.9.2)"]
test = ["Jinja2 (>=2.3)", "Pygments (>=1.2)", "backports.zoneinfo", "docutils (>=0.10)", "flake8 (>=2.4.0)", "flexmock (>=0.9.7)", "isort (>=4.2.2)", "pg8000 (>=1.12.4)", "psycopg (>=3.1.8)", "psycopg2 (>=2.5.1)", "psycopg2cffi (>=2.8.1)", "pymysql", "pyodbc", "pytest (==7.4.4)", "python-dateutil (>=2.6)", "pytz (>=2014.2)"]
test-all = ["Babel (>=1.3)", "Jinja2 (>=2.3)", "Pygments (>=1.2)", "arrow (>=0.3.4)", "backports.zoneinfo", "colour (>=0.0.4)", "cryptography (>=0.6)", "docutils (>=0.10)", "flake8 (>=2.4.0)", "flexmock (>=0.9.7)", "furl (>=0.4.1)", "intervals (>=0.7.1)", "isort (>=4.2.2)", "passlib (>=1.6,<2.0)", "pendulum (>=2.0.5)", "pg8000 (>=1.12.4)", "phonenumbers (>=5.9.2)", "psycopg (>=3.1.8)", "psycopg2 (>=2.5.1)", "psycopg2cffi (>=2.8.1)", "pymysql", "pyodbc", "pytest (==7.4.4)", "python-dateutil", "python-dateutil (>=2.6)", "pytz (>=2014.2)"]
timezone = ["python-dateutil"]
url = ["furl (>=0.4.1)"]
[[package]]
name = "sqlmodel"
version = "0.0.19"
@ -3871,4 +3914,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools",
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "76c4defc807fe83375766ac085982a2edf16e57b2d092a4494021f00a0424a4c"
content-hash = "49d72c6871e3ecffae3b55ccad3a6b140f9a1ebbca84d7632dafd54e1d2b7f9d"

View file

@ -39,6 +39,8 @@ uvicorn = "0.30.1"
typer = "^0.12.3"
chromadb = "^0.5.3"
alembic = "^1.13.2"
alembic-postgresql-enum = "^1.2.0"
sqlalchemy-utils = "^0.41.2"
[build-system]
build-backend = "poetry.core.masonry.api"

View file

@ -5,8 +5,12 @@ from sqlmodel import Field, SQLModel
class CollectionCourse(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
collection_id: int = Field(sa_column=Column(Integer, ForeignKey("collection.id", ondelete="CASCADE")))
course_id: int = Field(sa_column=Column(Integer, ForeignKey("course.id", ondelete="CASCADE")))
collection_id: int = Field(
sa_column=Column(Integer, ForeignKey("collection.id", ondelete="CASCADE"))
)
course_id: int = Field(
sa_column=Column(Integer, ForeignKey("course.id", ondelete="CASCADE"))
)
org_id: int = Field(default=None, foreign_key="organization.id")
creation_date: str
update_date: str

View file

@ -87,7 +87,7 @@ class Assignment(AssignmentBase, table=True):
class AssignmentTaskTypeEnum(str, Enum):
FILE_SUBMISSION = "FILE_SUBMISSION"
QUIZ = "QUIZ"
FORM = "FORM" # soon to be implemented
FORM = "FORM" # soon to be implemented
OTHER = "OTHER"

View file

@ -16,6 +16,12 @@ class TrailBase(SQLModel):
class Trail(TrailBase, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
org_id: int = Field(
sa_column=Column(Integer, ForeignKey("organization.id", ondelete="CASCADE"))
)
user_id: int = Field(
sa_column=Column(Integer, ForeignKey("user.id", ondelete="CASCADE"))
)
trail_uuid: str = ""
creation_date: str = ""
update_date: str = ""

View file

@ -17,6 +17,7 @@ from src.services.courses.activities.assignments import (
create_assignment_task,
create_assignment_task_submission,
delete_assignment,
delete_assignment_from_activity_uuid,
delete_assignment_submission,
delete_assignment_task,
delete_assignment_task_submission,
@ -90,6 +91,18 @@ async def api_delete_assignment(
"""
return await delete_assignment(request, assignment_uuid, current_user, db_session)
@router.delete("/activity/{activity_uuid}")
async def api_delete_assignment_from_activity(
request: Request,
activity_uuid: str,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
):
"""
Delete an assignment
"""
return await delete_assignment_from_activity_uuid(request, activity_uuid, current_user, db_session)
## ASSIGNMENTS Tasks ##

View file

@ -8,6 +8,7 @@ from uuid import uuid4
from fastapi import HTTPException, Request
from sqlmodel import Session, select
from src.db.courses.activities import Activity
from src.db.courses.assignments import (
Assignment,
AssignmentCreate,
@ -184,6 +185,53 @@ async def delete_assignment(
return {"message": "Assignment deleted"}
async def delete_assignment_from_activity_uuid(
request: Request,
activity_uuid: str,
current_user: PublicUser | AnonymousUser,
db_session: Session,
):
# Check if activity exists
statement = select(Activity).where(Activity.activity_uuid == activity_uuid)
activity = db_session.exec(statement).first()
if not activity:
raise HTTPException(
status_code=404,
detail="Activity not found",
)
# Check if course exists
statement = select(Course).where(Course.id == activity.course_id)
course = db_session.exec(statement).first()
if not course:
raise HTTPException(
status_code=404,
detail="Course not found",
)
# Check if assignment exists
statement = select(Assignment).where(Assignment.activity_id == activity.id)
assignment = db_session.exec(statement).first()
if not assignment:
raise HTTPException(
status_code=404,
detail="Assignment not found",
)
# RBAC check
await rbac_check(request, course.course_uuid, current_user, "delete", db_session)
# Delete Assignment
db_session.delete(assignment)
db_session.commit()
return {"message": "Assignment deleted"}
## > Assignments Tasks CRUD