From c110c2fb4f7ea178162a9450bcd0d7d9c22e8410 Mon Sep 17 00:00:00 2001 From: Dmitry Abramov Date: Mon, 11 Dec 2023 01:38:23 +0700 Subject: [PATCH 01/20] =?UTF-8?q?=D0=93=D1=80=D1=83=D0=BF=D0=BF=D0=B0=20?= =?UTF-8?q?=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D1=91=D1=82=D1=81=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- alembic/versions/ce2a08bc6d33_group_model.py | 34 ++++++++++++++++++++ app/api/endpoints/__init__.py | 1 + app/api/endpoints/group.py | 26 +++++++++++++++ app/api/routers.py | 3 +- app/core/base.py | 2 +- app/crud/__init__.py | 1 + app/crud/group.py | 9 ++++++ app/models/__init__.py | 1 + app/models/group.py | 10 ++++++ app/schemas/group.py | 13 ++++++++ 10 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 alembic/versions/ce2a08bc6d33_group_model.py create mode 100644 app/api/endpoints/group.py create mode 100644 app/crud/__init__.py create mode 100644 app/crud/group.py create mode 100644 app/models/group.py create mode 100644 app/schemas/group.py diff --git a/alembic/versions/ce2a08bc6d33_group_model.py b/alembic/versions/ce2a08bc6d33_group_model.py new file mode 100644 index 0000000..d12bdd9 --- /dev/null +++ b/alembic/versions/ce2a08bc6d33_group_model.py @@ -0,0 +1,34 @@ +"""group model + +Revision ID: ce2a08bc6d33 +Revises: d532edc24b0c +Create Date: 2023-12-11 00:21:21.862553 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'ce2a08bc6d33' +down_revision = 'd532edc24b0c' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('group', + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('group') + # ### end Alembic commands ### diff --git a/app/api/endpoints/__init__.py b/app/api/endpoints/__init__.py index 0c5f000..4682af2 100644 --- a/app/api/endpoints/__init__.py +++ b/app/api/endpoints/__init__.py @@ -1 +1,2 @@ from .user import router as user_router # noqa +from .group import router as group_router # noqa diff --git a/app/api/endpoints/group.py b/app/api/endpoints/group.py new file mode 100644 index 0000000..3a9197c --- /dev/null +++ b/app/api/endpoints/group.py @@ -0,0 +1,26 @@ +from typing import List + +from fastapi import APIRouter, Depends +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.db import get_async_session +from app.crud import group_crud +from app.schemas.group import GroupCreate, GroupRead + + +router = APIRouter() + + +@router.post('/') +async def create_group( + group: GroupCreate, + session: AsyncSession = Depends(get_async_session) +): + return await group_crud.create(group, session=session) + + +@router.get('/', response_model=List[GroupRead]) +async def get_all_groups( + session: AsyncSession = Depends(get_async_session) +): + return await group_crud.get_multi(session=session) diff --git a/app/api/routers.py b/app/api/routers.py index 1dc9cf8..626cc90 100644 --- a/app/api/routers.py +++ b/app/api/routers.py @@ -1,9 +1,10 @@ from fastapi import APIRouter from app.api.endpoints import ( - user_router, + user_router, group_router, ) main_router = APIRouter() main_router.include_router(user_router) +main_router.include_router(group_router, prefix='/groups', tags=['Group']) diff --git a/app/core/base.py b/app/core/base.py index e429740..ee7e51f 100644 --- a/app/core/base.py +++ b/app/core/base.py @@ -1,2 +1,2 @@ from app.core.db import Base # noqa -from app.models import User # noqa \ No newline at end of file +from app.models import Group, User # noqa \ No newline at end of file diff --git a/app/crud/__init__.py b/app/crud/__init__.py new file mode 100644 index 0000000..46c3576 --- /dev/null +++ b/app/crud/__init__.py @@ -0,0 +1 @@ +from .group import group_crud # noqa diff --git a/app/crud/group.py b/app/crud/group.py new file mode 100644 index 0000000..54cda8a --- /dev/null +++ b/app/crud/group.py @@ -0,0 +1,9 @@ +from app.crud.base import CRUDBase +from app.models import Group + + +class CRUDGroup(CRUDBase): + pass + + +group_crud = CRUDGroup(Group) diff --git a/app/models/__init__.py b/app/models/__init__.py index a5057a0..54823d6 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -1 +1,2 @@ from .user import User # noqa +from .group import Group # noqa diff --git a/app/models/group.py b/app/models/group.py new file mode 100644 index 0000000..4c11932 --- /dev/null +++ b/app/models/group.py @@ -0,0 +1,10 @@ +from sqlalchemy import Column, String, Text + +from app.core.db import Base +from app.core.config import settings + + +class Group(Base): + name = Column(String(length=settings.max_length_string), unique=True, + nullable=False) + description = Column(Text) diff --git a/app/schemas/group.py b/app/schemas/group.py new file mode 100644 index 0000000..cbb08f4 --- /dev/null +++ b/app/schemas/group.py @@ -0,0 +1,13 @@ +from typing import Optional + +from pydantic import BaseModel + + +class GroupCreate(BaseModel): + name: str + description: Optional[str] + + +class GroupRead(BaseModel): + name: Optional[str] + description: Optional[str] From 3f13d41d052e4f71eab02c5f6b40433b2319dbfd Mon Sep 17 00:00:00 2001 From: Dmitry Abramov Date: Mon, 11 Dec 2023 21:28:47 +0700 Subject: [PATCH 02/20] create tariff and profile models --- .../versions/5b37c27efb7b_group_user_m2m.py | 28 +++++++++++++ .../8df1f3e7f3d4_tariff_and_profile_models.py | 42 +++++++++++++++++++ .../versions/fd614408777f_group_user_m2m_4.py | 33 +++++++++++++++ app/api/endpoints/group.py | 23 +++++++++- app/core/base.py | 4 +- app/crud/__init__.py | 1 + app/crud/user.py | 9 ++++ app/models/__init__.py | 2 + app/models/group.py | 22 ++++++++-- app/models/profile.py | 11 +++++ app/models/tariff.py | 11 +++++ app/models/user.py | 5 ++- app/schemas/group.py | 6 ++- 13 files changed, 188 insertions(+), 9 deletions(-) create mode 100644 alembic/versions/5b37c27efb7b_group_user_m2m.py create mode 100644 alembic/versions/8df1f3e7f3d4_tariff_and_profile_models.py create mode 100644 alembic/versions/fd614408777f_group_user_m2m_4.py create mode 100644 app/crud/user.py create mode 100644 app/models/profile.py create mode 100644 app/models/tariff.py diff --git a/alembic/versions/5b37c27efb7b_group_user_m2m.py b/alembic/versions/5b37c27efb7b_group_user_m2m.py new file mode 100644 index 0000000..0b9a2fd --- /dev/null +++ b/alembic/versions/5b37c27efb7b_group_user_m2m.py @@ -0,0 +1,28 @@ +"""group user m2m + +Revision ID: 5b37c27efb7b +Revises: ce2a08bc6d33 +Create Date: 2023-12-11 02:23:26.166882 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '5b37c27efb7b' +down_revision = 'ce2a08bc6d33' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/alembic/versions/8df1f3e7f3d4_tariff_and_profile_models.py b/alembic/versions/8df1f3e7f3d4_tariff_and_profile_models.py new file mode 100644 index 0000000..744d2aa --- /dev/null +++ b/alembic/versions/8df1f3e7f3d4_tariff_and_profile_models.py @@ -0,0 +1,42 @@ +"""tariff and profile models + +Revision ID: 8df1f3e7f3d4 +Revises: fd614408777f +Create Date: 2023-12-11 21:24:27.374213 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '8df1f3e7f3d4' +down_revision = 'fd614408777f' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('profile', + sa.Column('first_name', sa.String(length=100), nullable=True), + sa.Column('last_name', sa.String(length=100), nullable=True), + sa.Column('age', sa.SmallInteger(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('tariff', + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('tariff') + op.drop_table('profile') + # ### end Alembic commands ### diff --git a/alembic/versions/fd614408777f_group_user_m2m_4.py b/alembic/versions/fd614408777f_group_user_m2m_4.py new file mode 100644 index 0000000..ebe60ff --- /dev/null +++ b/alembic/versions/fd614408777f_group_user_m2m_4.py @@ -0,0 +1,33 @@ +"""group user m2m 4 + +Revision ID: fd614408777f +Revises: 5b37c27efb7b +Create Date: 2023-12-11 02:29:57.140105 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'fd614408777f' +down_revision = '5b37c27efb7b' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('association_table', + sa.Column('group_id', sa.Integer(), nullable=True), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['group_id'], ['group.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ) + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('association_table') + # ### end Alembic commands ### diff --git a/app/api/endpoints/group.py b/app/api/endpoints/group.py index 3a9197c..278db83 100644 --- a/app/api/endpoints/group.py +++ b/app/api/endpoints/group.py @@ -4,8 +4,9 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.core.db import get_async_session -from app.crud import group_crud +from app.crud import group_crud, user_crud from app.schemas.group import GroupCreate, GroupRead +from app.models import Group router = APIRouter() @@ -24,3 +25,23 @@ async def get_all_groups( session: AsyncSession = Depends(get_async_session) ): return await group_crud.get_multi(session=session) + + +@router.get('/{group_id}', response_model=GroupRead) +async def get_group( + group_id: int, + session: AsyncSession = Depends(get_async_session) +): + return await group_crud.get(group_id, session=session) + + +# @router.post('/{group_id}', response_model=GroupRead) +# async def set_user_in_group( +# group_id: int, +# user_id: int, +# session: AsyncSession = Depends(get_async_session) +# ): +# user = await user_crud.get(user_id, session=session) +# group: Group = await group_crud.get(group_id, session=session) +# group.user.append(user) +# # TODO \ No newline at end of file diff --git a/app/core/base.py b/app/core/base.py index ee7e51f..0b1ac91 100644 --- a/app/core/base.py +++ b/app/core/base.py @@ -1,2 +1,4 @@ from app.core.db import Base # noqa -from app.models import Group, User # noqa \ No newline at end of file +from app.models import ( # noqa + Group, User, Profile, Tariff +) diff --git a/app/crud/__init__.py b/app/crud/__init__.py index 46c3576..219a33b 100644 --- a/app/crud/__init__.py +++ b/app/crud/__init__.py @@ -1 +1,2 @@ from .group import group_crud # noqa +from .user import user_crud # noqa diff --git a/app/crud/user.py b/app/crud/user.py new file mode 100644 index 0000000..3a9d35d --- /dev/null +++ b/app/crud/user.py @@ -0,0 +1,9 @@ +from app.crud.base import CRUDBase +from app.models import User + + +class CRUDUser(CRUDBase): + pass + + +user_crud = CRUDUser(User) diff --git a/app/models/__init__.py b/app/models/__init__.py index 54823d6..1552c92 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -1,2 +1,4 @@ from .user import User # noqa from .group import Group # noqa +from .profile import Profile # noqa +from .tariff import Tariff # noqa diff --git a/app/models/group.py b/app/models/group.py index 4c11932..7f8ad8d 100644 --- a/app/models/group.py +++ b/app/models/group.py @@ -1,10 +1,24 @@ -from sqlalchemy import Column, String, Text +from __future__ import annotations + +from typing import List + +from sqlalchemy import Column, String, Text, Table, ForeignKey +from sqlalchemy.orm import Mapped, relationship from app.core.db import Base from app.core.config import settings +from app.models import User + + +association_table = Table( + 'association_table', Base.metadata, + Column('group_id', ForeignKey('group.id')), + Column('user_id', ForeignKey('user.id')) +) class Group(Base): - name = Column(String(length=settings.max_length_string), unique=True, - nullable=False) - description = Column(Text) + name: str = Column(String(length=settings.max_length_string), unique=True, + nullable=False) + description: str = Column(Text) + user: Mapped[List[User]] = relationship(secondary=association_table) diff --git a/app/models/profile.py b/app/models/profile.py new file mode 100644 index 0000000..6690985 --- /dev/null +++ b/app/models/profile.py @@ -0,0 +1,11 @@ +from sqlalchemy import Column, String, SmallInteger +from sqlalchemy.orm import Mapped + +from app.core.db import Base +from app.core.config import settings + + +class Profile(Base): + first_name: Mapped[str] = Column(String(length=settings.max_length_string)) + last_name: Mapped[str] = Column(String(length=settings.max_length_string)) + age: Mapped[int] = Column(SmallInteger) diff --git a/app/models/tariff.py b/app/models/tariff.py new file mode 100644 index 0000000..dbdbfd2 --- /dev/null +++ b/app/models/tariff.py @@ -0,0 +1,11 @@ +from sqlalchemy import Column, String, Text +from sqlalchemy.orm import Mapped + +from app.core.db import Base +from app.core.config import settings + + +class Tariff(Base): + name: Mapped[str] = Column(String(length=settings.max_length_string), + unique=True, nullable=False) + description: Mapped[str] = Column(Text) diff --git a/app/models/user.py b/app/models/user.py index 921addc..0c73d78 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -2,6 +2,7 @@ from fastapi_users_db_sqlalchemy import SQLAlchemyBaseUserTable from sqlalchemy import Enum, Column, String +from sqlalchemy.orm import Mapped from app.core.db import Base @@ -13,7 +14,7 @@ class UserRoleEnum(enum.Enum): class User(SQLAlchemyBaseUserTable[int], Base): - role = Column( + role: Mapped[str] = Column( Enum(UserRoleEnum), default=UserRoleEnum.user, nullable=False ) - username = Column(String(length=100), nullable=False) + username: Mapped[str] = Column(String(length=100), nullable=False) diff --git a/app/schemas/group.py b/app/schemas/group.py index cbb08f4..0e347f3 100644 --- a/app/schemas/group.py +++ b/app/schemas/group.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, List from pydantic import BaseModel @@ -9,5 +9,9 @@ class GroupCreate(BaseModel): class GroupRead(BaseModel): + id: int name: Optional[str] description: Optional[str] + + class Config: + orm_mode = True From ac1d953fe1a78e26e95866da01e670d7059b10d4 Mon Sep 17 00:00:00 2001 From: Dmitry Abramov Date: Mon, 11 Dec 2023 22:04:40 +0700 Subject: [PATCH 03/20] relationship user tariff --- .../e21da4ecec18_foreingkey_user_teriff.py | 34 +++++++++++++++++++ app/models/tariff.py | 8 ++++- app/models/user.py | 12 +++++-- 3 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 alembic/versions/e21da4ecec18_foreingkey_user_teriff.py diff --git a/alembic/versions/e21da4ecec18_foreingkey_user_teriff.py b/alembic/versions/e21da4ecec18_foreingkey_user_teriff.py new file mode 100644 index 0000000..c040e1a --- /dev/null +++ b/alembic/versions/e21da4ecec18_foreingkey_user_teriff.py @@ -0,0 +1,34 @@ +"""foreingkey user teriff + +Revision ID: e21da4ecec18 +Revises: 8df1f3e7f3d4 +Create Date: 2023-12-11 21:52:01.340149 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'e21da4ecec18' +down_revision = '8df1f3e7f3d4' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('user', schema=None) as batch_op: + batch_op.add_column(sa.Column('tariff_id', sa.Integer(), nullable=True)) + batch_op.create_foreign_key('fk_user_tariff', 'tariff', ['tariff_id'], ['id']) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('user', schema=None) as batch_op: + batch_op.drop_constraint(None, type_='foreignkey') + batch_op.drop_column('tariff_id') + + # ### end Alembic commands ### diff --git a/app/models/tariff.py b/app/models/tariff.py index dbdbfd2..44c36d1 100644 --- a/app/models/tariff.py +++ b/app/models/tariff.py @@ -1,11 +1,17 @@ +from typing import TYPE_CHECKING + from sqlalchemy import Column, String, Text -from sqlalchemy.orm import Mapped +from sqlalchemy.orm import Mapped, relationship from app.core.db import Base from app.core.config import settings +if TYPE_CHECKING: + from .user import User + class Tariff(Base): name: Mapped[str] = Column(String(length=settings.max_length_string), unique=True, nullable=False) description: Mapped[str] = Column(Text) + users: Mapped[list['User']] = relationship(back_populates='users') diff --git a/app/models/user.py b/app/models/user.py index 0c73d78..465b89b 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -1,11 +1,15 @@ import enum +from typing import TYPE_CHECKING from fastapi_users_db_sqlalchemy import SQLAlchemyBaseUserTable -from sqlalchemy import Enum, Column, String -from sqlalchemy.orm import Mapped +from sqlalchemy import Enum, Column, String, ForeignKey +from sqlalchemy.orm import Mapped, mapped_column, relationship from app.core.db import Base +if TYPE_CHECKING: + from .tariff import Tariff + class UserRoleEnum(enum.Enum): user = 'user' @@ -18,3 +22,7 @@ class User(SQLAlchemyBaseUserTable[int], Base): Enum(UserRoleEnum), default=UserRoleEnum.user, nullable=False ) username: Mapped[str] = Column(String(length=100), nullable=False) + tariff_id: Mapped[int] = Column( + ForeignKey('tariff.id'), + ) + tariff: Mapped['Tariff'] = relationship(back_populates='users') From c989b18a71aac6525b73b2ef6fc4b001adf2f1db Mon Sep 17 00:00:00 2001 From: Dmitry Abramov Date: Tue, 12 Dec 2023 00:02:18 +0700 Subject: [PATCH 04/20] relationship user profile --- .../9436e9b65a6a_relationship_user_profile.py | 36 +++++++++++++++++++ app/models/profile.py | 13 +++++-- app/models/tariff.py | 2 +- app/models/user.py | 2 ++ app/schemas/user.py | 4 +++ 5 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 alembic/versions/9436e9b65a6a_relationship_user_profile.py diff --git a/alembic/versions/9436e9b65a6a_relationship_user_profile.py b/alembic/versions/9436e9b65a6a_relationship_user_profile.py new file mode 100644 index 0000000..df9fc72 --- /dev/null +++ b/alembic/versions/9436e9b65a6a_relationship_user_profile.py @@ -0,0 +1,36 @@ +"""relationship user profile + +Revision ID: 9436e9b65a6a +Revises: e21da4ecec18 +Create Date: 2023-12-11 22:56:24.072674 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '9436e9b65a6a' +down_revision = 'e21da4ecec18' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('profile', schema=None) as batch_op: + batch_op.add_column(sa.Column('user_id', sa.Integer(), nullable=True)) + batch_op.create_unique_constraint('constraint_user', ['user_id']) + batch_op.create_foreign_key('fk_user_profile', 'user', ['user_id'], ['id']) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('profile', schema=None) as batch_op: + batch_op.drop_constraint('fk_user_profile', type_='foreignkey') + batch_op.drop_constraint('constraint_user', type_='unique') + batch_op.drop_column('user_id') + + # ### end Alembic commands ### diff --git a/app/models/profile.py b/app/models/profile.py index 6690985..480ef07 100644 --- a/app/models/profile.py +++ b/app/models/profile.py @@ -1,11 +1,20 @@ -from sqlalchemy import Column, String, SmallInteger -from sqlalchemy.orm import Mapped +from typing import TYPE_CHECKING + +from sqlalchemy import Column, String, SmallInteger, ForeignKey +from sqlalchemy.orm import Mapped, relationship from app.core.db import Base from app.core.config import settings +if TYPE_CHECKING: + from .user import User + class Profile(Base): first_name: Mapped[str] = Column(String(length=settings.max_length_string)) last_name: Mapped[str] = Column(String(length=settings.max_length_string)) age: Mapped[int] = Column(SmallInteger) + user_id: Mapped[int] = Column( + ForeignKey('user.id'), unique=True + ) + user: Mapped['User'] = relationship(back_populates='profile') diff --git a/app/models/tariff.py b/app/models/tariff.py index 44c36d1..4396c3e 100644 --- a/app/models/tariff.py +++ b/app/models/tariff.py @@ -14,4 +14,4 @@ class Tariff(Base): name: Mapped[str] = Column(String(length=settings.max_length_string), unique=True, nullable=False) description: Mapped[str] = Column(Text) - users: Mapped[list['User']] = relationship(back_populates='users') + users: Mapped[list['User'] | None] = relationship(back_populates='tariff') diff --git a/app/models/user.py b/app/models/user.py index 465b89b..bbebb0c 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -9,6 +9,7 @@ if TYPE_CHECKING: from .tariff import Tariff + from .profile import Profile class UserRoleEnum(enum.Enum): @@ -26,3 +27,4 @@ class User(SQLAlchemyBaseUserTable[int], Base): ForeignKey('tariff.id'), ) tariff: Mapped['Tariff'] = relationship(back_populates='users') + profile: Mapped['Profile'] = relationship(back_populates='user') diff --git a/app/schemas/user.py b/app/schemas/user.py index 8769243..575d383 100644 --- a/app/schemas/user.py +++ b/app/schemas/user.py @@ -13,6 +13,10 @@ class Role(str, Enum): class UserRead(schemas.BaseUser[int]): role: Role username: str + tariff_id: Optional[int] + + class Config: + orm_mode = True class UserCreate(schemas.BaseUserCreate): From 9c3afc768fb350e02544137637faca3e1a77d987 Mon Sep 17 00:00:00 2001 From: Dmitry Abramov Date: Tue, 12 Dec 2023 01:24:48 +0700 Subject: [PATCH 05/20] =?UTF-8?q?=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D1=8D=D0=BD=D0=B4=D0=BF=D0=BE=D0=B8=D0=BD=D1=82=D1=8B=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=BF=D1=80=D0=BE=D1=84=D0=B8=D0=BB=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/endpoints/__init__.py | 1 + app/api/endpoints/group.py | 15 +-------------- app/api/endpoints/profile.py | 25 +++++++++++++++++++++++++ app/api/routers.py | 4 +++- app/crud/__init__.py | 1 + app/crud/profile.py | 22 ++++++++++++++++++++++ app/models/user.py | 2 +- app/schemas/group.py | 4 ++-- app/schemas/profile.py | 20 ++++++++++++++++++++ app/schemas/user.py | 2 +- 10 files changed, 77 insertions(+), 19 deletions(-) create mode 100644 app/api/endpoints/profile.py create mode 100644 app/crud/profile.py create mode 100644 app/schemas/profile.py diff --git a/app/api/endpoints/__init__.py b/app/api/endpoints/__init__.py index 4682af2..375cec7 100644 --- a/app/api/endpoints/__init__.py +++ b/app/api/endpoints/__init__.py @@ -1,2 +1,3 @@ from .user import router as user_router # noqa from .group import router as group_router # noqa +from .profile import router as profile_router # noqa diff --git a/app/api/endpoints/group.py b/app/api/endpoints/group.py index 278db83..7592ef4 100644 --- a/app/api/endpoints/group.py +++ b/app/api/endpoints/group.py @@ -4,9 +4,8 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.core.db import get_async_session -from app.crud import group_crud, user_crud +from app.crud import group_crud from app.schemas.group import GroupCreate, GroupRead -from app.models import Group router = APIRouter() @@ -33,15 +32,3 @@ async def get_group( session: AsyncSession = Depends(get_async_session) ): return await group_crud.get(group_id, session=session) - - -# @router.post('/{group_id}', response_model=GroupRead) -# async def set_user_in_group( -# group_id: int, -# user_id: int, -# session: AsyncSession = Depends(get_async_session) -# ): -# user = await user_crud.get(user_id, session=session) -# group: Group = await group_crud.get(group_id, session=session) -# group.user.append(user) -# # TODO \ No newline at end of file diff --git a/app/api/endpoints/profile.py b/app/api/endpoints/profile.py new file mode 100644 index 0000000..4c9706d --- /dev/null +++ b/app/api/endpoints/profile.py @@ -0,0 +1,25 @@ +from fastapi import APIRouter, Depends +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.db import get_async_session +from app.crud import profile_crud +from app.schemas.profile import ProfileCreate, ProfileRead + + +router = APIRouter() + + +@router.post('/{user_id}', response_model=ProfileRead) +async def create_profile( + user_id: int, + profile: ProfileCreate, + session: AsyncSession = Depends(get_async_session), +): + return await profile_crud.create(profile, user_id, session) + + +@router.get('/', response_model=list[ProfileRead]) +async def get_all_profiles( + session: AsyncSession = Depends(get_async_session) +): + return await profile_crud.get_multi(session) diff --git a/app/api/routers.py b/app/api/routers.py index 626cc90..5f001c9 100644 --- a/app/api/routers.py +++ b/app/api/routers.py @@ -1,10 +1,12 @@ from fastapi import APIRouter from app.api.endpoints import ( - user_router, group_router, + user_router, group_router, profile_router ) main_router = APIRouter() main_router.include_router(user_router) main_router.include_router(group_router, prefix='/groups', tags=['Group']) +main_router.include_router(profile_router, prefix='/profiles', + tags=['Profile']) diff --git a/app/crud/__init__.py b/app/crud/__init__.py index 219a33b..d296454 100644 --- a/app/crud/__init__.py +++ b/app/crud/__init__.py @@ -1,2 +1,3 @@ from .group import group_crud # noqa from .user import user_crud # noqa +from .profile import profile_crud # noqa diff --git a/app/crud/profile.py b/app/crud/profile.py new file mode 100644 index 0000000..db8f174 --- /dev/null +++ b/app/crud/profile.py @@ -0,0 +1,22 @@ +from sqlalchemy.ext.asyncio import AsyncSession + +from app.crud.base import CRUDBase +from app.models import Profile +from app.schemas.profile import ProfileCreate + + +class CRUDProfile(CRUDBase): + + async def create( + self, obj_in: ProfileCreate, user_id: int, session: AsyncSession + ): + obj_in_data: dict = obj_in.model_dump() + obj_in_data['user_id'] = user_id + db_obj = Profile(**obj_in_data) + session.add(db_obj) + await session.commit() + await session.refresh(db_obj) + return db_obj + + +profile_crud = CRUDProfile(Profile) diff --git a/app/models/user.py b/app/models/user.py index bbebb0c..c02d391 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -3,7 +3,7 @@ from fastapi_users_db_sqlalchemy import SQLAlchemyBaseUserTable from sqlalchemy import Enum, Column, String, ForeignKey -from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.orm import Mapped, relationship from app.core.db import Base diff --git a/app/schemas/group.py b/app/schemas/group.py index 0e347f3..f1b55d3 100644 --- a/app/schemas/group.py +++ b/app/schemas/group.py @@ -1,4 +1,4 @@ -from typing import Optional, List +from typing import Optional from pydantic import BaseModel @@ -14,4 +14,4 @@ class GroupRead(BaseModel): description: Optional[str] class Config: - orm_mode = True + from_attributes = True diff --git a/app/schemas/profile.py b/app/schemas/profile.py new file mode 100644 index 0000000..9122095 --- /dev/null +++ b/app/schemas/profile.py @@ -0,0 +1,20 @@ +from typing import Optional + +from pydantic import BaseModel + + +class ProfileRead(BaseModel): + id: int + first_name: Optional[str] + last_name: Optional[str] + age: Optional[int] + user_id: int + + class Config: + from_attributes = True + + +class ProfileCreate(BaseModel): + first_name: Optional[str] + last_name: Optional[str] + age: Optional[int] diff --git a/app/schemas/user.py b/app/schemas/user.py index 575d383..bba2b33 100644 --- a/app/schemas/user.py +++ b/app/schemas/user.py @@ -16,7 +16,7 @@ class UserRead(schemas.BaseUser[int]): tariff_id: Optional[int] class Config: - orm_mode = True + from_attributes = True class UserCreate(schemas.BaseUserCreate): From 1b742e3bddd562a8485ff4258fdaf48ae00e3a63 Mon Sep 17 00:00:00 2001 From: Dmitry Abramov Date: Tue, 12 Dec 2023 03:23:36 +0700 Subject: [PATCH 06/20] create tariff crud, scheme, endpoints --- app/api/endpoints/__init__.py | 1 + app/api/endpoints/tariff.py | 24 ++++++++++++++++++++++++ app/api/routers.py | 3 ++- app/crud/__init__.py | 1 + app/crud/tariff.py | 9 +++++++++ app/schemas/tariff.py | 25 +++++++++++++++++++++++++ 6 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 app/api/endpoints/tariff.py create mode 100644 app/crud/tariff.py create mode 100644 app/schemas/tariff.py diff --git a/app/api/endpoints/__init__.py b/app/api/endpoints/__init__.py index 375cec7..f7b3684 100644 --- a/app/api/endpoints/__init__.py +++ b/app/api/endpoints/__init__.py @@ -1,3 +1,4 @@ from .user import router as user_router # noqa from .group import router as group_router # noqa from .profile import router as profile_router # noqa +from .tariff import router as tariff_router # noqa diff --git a/app/api/endpoints/tariff.py b/app/api/endpoints/tariff.py new file mode 100644 index 0000000..359a3c5 --- /dev/null +++ b/app/api/endpoints/tariff.py @@ -0,0 +1,24 @@ +from fastapi import APIRouter, Depends +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.db import get_async_session +from app.crud import tariff_crud +from app.schemas.tariff import TarifCreate, TariffRead, TariffCreated + + +router = APIRouter() + + +@router.get('/', response_model=list[TariffRead]) +async def get_tariffs( + session: AsyncSession = Depends(get_async_session) +): + return await tariff_crud.get_multi(session) + + +@router.post('/', response_model=TariffCreated) +async def create_tariff( + tariff: TarifCreate, + session: AsyncSession = Depends(get_async_session), +): + return await tariff_crud.create(tariff, session) diff --git a/app/api/routers.py b/app/api/routers.py index 5f001c9..00c0306 100644 --- a/app/api/routers.py +++ b/app/api/routers.py @@ -1,7 +1,7 @@ from fastapi import APIRouter from app.api.endpoints import ( - user_router, group_router, profile_router + user_router, group_router, profile_router, tariff_router ) main_router = APIRouter() @@ -10,3 +10,4 @@ main_router.include_router(group_router, prefix='/groups', tags=['Group']) main_router.include_router(profile_router, prefix='/profiles', tags=['Profile']) +main_router.include_router(tariff_router, prefix='/tariffs', tags=['Tariff']) diff --git a/app/crud/__init__.py b/app/crud/__init__.py index d296454..5e11242 100644 --- a/app/crud/__init__.py +++ b/app/crud/__init__.py @@ -1,3 +1,4 @@ from .group import group_crud # noqa from .user import user_crud # noqa from .profile import profile_crud # noqa +from .tariff import tariff_crud # noqa diff --git a/app/crud/tariff.py b/app/crud/tariff.py new file mode 100644 index 0000000..3e55a7f --- /dev/null +++ b/app/crud/tariff.py @@ -0,0 +1,9 @@ +from app.crud.base import CRUDBase +from app.models import Tariff + + +class CRUDTariff(CRUDBase): + pass + + +tariff_crud = CRUDTariff(Tariff) diff --git a/app/schemas/tariff.py b/app/schemas/tariff.py new file mode 100644 index 0000000..d71f987 --- /dev/null +++ b/app/schemas/tariff.py @@ -0,0 +1,25 @@ +from typing import Optional + +from pydantic import BaseModel, Field + + +class TariffRead(BaseModel): + id: int + name: str + description: Optional[str] + users: Optional[list[int]] + + class Config: + from_attributes = True + + +class TarifCreate(BaseModel): + name: str + description: Optional[str] + + +class TariffCreated(TarifCreate): + id: int + + class Config: + from_attributes = True From 60c38f7c8b8e7125df6cb38643ccbfbd6bbed21e Mon Sep 17 00:00:00 2001 From: Dmitry Abramov Date: Tue, 12 Dec 2023 03:24:27 +0700 Subject: [PATCH 07/20] =?UTF-8?q?=D1=83=D0=B4=D0=B0=D0=BB=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BB=D0=B8=D1=88=D0=BD=D0=B8=D0=B5=20=D0=B8=D0=BC=D0=BF=D0=BE?= =?UTF-8?q?=D1=80=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/schemas/tariff.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/schemas/tariff.py b/app/schemas/tariff.py index d71f987..8a81b2e 100644 --- a/app/schemas/tariff.py +++ b/app/schemas/tariff.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import BaseModel, Field +from pydantic import BaseModel class TariffRead(BaseModel): From 5e6b3fccb4a870f66d20b6d1280828807b5011d3 Mon Sep 17 00:00:00 2001 From: Dmitry Abramov Date: Tue, 12 Dec 2023 04:04:32 +0700 Subject: [PATCH 08/20] =?UTF-8?q?=D0=92=20=D1=81=D1=85=D0=B5=D0=BC=D0=B5?= =?UTF-8?q?=20TariffRead=20=D1=83=D0=B1=D1=80=D0=B0=D0=BB=20=D0=BE=D1=82?= =?UTF-8?q?=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=8E?= =?UTF-8?q?=D0=B7=D0=B5=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/schemas/tariff.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/schemas/tariff.py b/app/schemas/tariff.py index 8a81b2e..0f755b3 100644 --- a/app/schemas/tariff.py +++ b/app/schemas/tariff.py @@ -7,7 +7,6 @@ class TariffRead(BaseModel): id: int name: str description: Optional[str] - users: Optional[list[int]] class Config: from_attributes = True From b55bfbab3320d2a36e9503003965701065e821ba Mon Sep 17 00:00:00 2001 From: Dmitry Abramov Date: Tue, 12 Dec 2023 04:56:05 +0700 Subject: [PATCH 09/20] =?UTF-8?q?=D0=97=D0=B0=D0=BC=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BD=D0=B0=D0=B7=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=BC=D0=B5=D0=B6=D1=83=D1=82=D0=BE=D1=87?= =?UTF-8?q?=D0=BD=D0=BE=D0=B9=20=D0=B3=D1=80=D1=83=D0=BF=D0=BF=D1=8B=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D0=B5=D0=B9?= =?UTF-8?q?=20Group=20User?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В таблицу group_user_association добавил id и UniqueConstraint --- .../bbd832bbe092_group_user_m2m_fix.py | 34 ++++++++++++++ ...reload_association_table_in_group_user_.py | 45 +++++++++++++++++++ app/models/group.py | 20 ++++++--- app/models/user.py | 4 ++ 4 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 alembic/versions/bbd832bbe092_group_user_m2m_fix.py create mode 100644 alembic/versions/f195e92a07b3_reload_association_table_in_group_user_.py diff --git a/alembic/versions/bbd832bbe092_group_user_m2m_fix.py b/alembic/versions/bbd832bbe092_group_user_m2m_fix.py new file mode 100644 index 0000000..f6a57c4 --- /dev/null +++ b/alembic/versions/bbd832bbe092_group_user_m2m_fix.py @@ -0,0 +1,34 @@ +"""group user m2m fix + +Revision ID: bbd832bbe092 +Revises: 9436e9b65a6a +Create Date: 2023-12-12 04:32:06.067226 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'bbd832bbe092' +down_revision = '9436e9b65a6a' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('association_table', schema=None) as batch_op: + batch_op.add_column(sa.Column('id', sa.Integer(), nullable=False)) + batch_op.create_unique_constraint('constraint_group_user', ['group_id', 'user_id']) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('association_table', schema=None) as batch_op: + batch_op.drop_constraint('constraint_group_user', type_='unique') + batch_op.drop_column('id') + + # ### end Alembic commands ### diff --git a/alembic/versions/f195e92a07b3_reload_association_table_in_group_user_.py b/alembic/versions/f195e92a07b3_reload_association_table_in_group_user_.py new file mode 100644 index 0000000..dda6403 --- /dev/null +++ b/alembic/versions/f195e92a07b3_reload_association_table_in_group_user_.py @@ -0,0 +1,45 @@ +"""reload association_table in group_user_association + +Revision ID: f195e92a07b3 +Revises: bbd832bbe092 +Create Date: 2023-12-12 04:53:44.659421 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'f195e92a07b3' +down_revision = 'bbd832bbe092' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('group_user_association', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('group_id', sa.Integer(), nullable=True), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['group_id'], ['group.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('group_id', 'user_id', name='constraint_group_user') + ) + op.drop_table('association_table') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('association_table', + sa.Column('group_id', sa.INTEGER(), nullable=True), + sa.Column('user_id', sa.INTEGER(), nullable=True), + sa.Column('id', sa.INTEGER(), nullable=False), + sa.ForeignKeyConstraint(['group_id'], ['group.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.UniqueConstraint('group_id', 'user_id', name='constraint_group_user') + ) + op.drop_table('group_user_association') + # ### end Alembic commands ### diff --git a/app/models/group.py b/app/models/group.py index 7f8ad8d..d817489 100644 --- a/app/models/group.py +++ b/app/models/group.py @@ -1,19 +1,24 @@ from __future__ import annotations -from typing import List +from typing import List, TYPE_CHECKING -from sqlalchemy import Column, String, Text, Table, ForeignKey +from sqlalchemy import (Column, String, Text, Table, ForeignKey, Integer, + UniqueConstraint) from sqlalchemy.orm import Mapped, relationship from app.core.db import Base from app.core.config import settings -from app.models import User +if TYPE_CHECKING: + from app.models import User -association_table = Table( - 'association_table', Base.metadata, + +group_user_association = Table( + 'group_user_association', Base.metadata, + Column('id', Integer, primary_key=True), Column('group_id', ForeignKey('group.id')), - Column('user_id', ForeignKey('user.id')) + Column('user_id', ForeignKey('user.id')), + UniqueConstraint('group_id', 'user_id', name='constraint_group_user') ) @@ -21,4 +26,5 @@ class Group(Base): name: str = Column(String(length=settings.max_length_string), unique=True, nullable=False) description: str = Column(Text) - user: Mapped[List[User]] = relationship(secondary=association_table) + users: Mapped[List[User]] = relationship(secondary=group_user_association, + back_populates='groups') diff --git a/app/models/user.py b/app/models/user.py index c02d391..91b95ff 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -6,10 +6,12 @@ from sqlalchemy.orm import Mapped, relationship from app.core.db import Base +from .group import group_user_association if TYPE_CHECKING: from .tariff import Tariff from .profile import Profile + from .group import Group class UserRoleEnum(enum.Enum): @@ -28,3 +30,5 @@ class User(SQLAlchemyBaseUserTable[int], Base): ) tariff: Mapped['Tariff'] = relationship(back_populates='users') profile: Mapped['Profile'] = relationship(back_populates='user') + groups: Mapped['Group'] = relationship(secondary=group_user_association, + back_populates='users') From d324309c35867fc3555d1f49070e99a3d9505a53 Mon Sep 17 00:00:00 2001 From: Dmitry Abramov Date: Fri, 15 Dec 2023 11:01:26 +0700 Subject: [PATCH 10/20] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D1=8C=20Notification=20=D0=B8=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=BC=D0=B5=D0=B6=D1=83=D1=82=D0=BE=D1=87?= =?UTF-8?q?=D0=BD=D1=83=D1=8E=20notification=5Fuser=5Fassociation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Переделал миграции --- .../16786c7048d6_added_default_role.py | 36 ------- alembic/versions/41b55aea7689_choice_fix.py | 32 ------- .../versions/5b37c27efb7b_group_user_m2m.py | 28 ------ .../8df1f3e7f3d4_tariff_and_profile_models.py | 42 --------- .../9436e9b65a6a_relationship_user_profile.py | 36 ------- .../bbd832bbe092_group_user_m2m_fix.py | 34 ------- alembic/versions/ce2a08bc6d33_group_model.py | 34 ------- alembic/versions/d532edc24b0c_username.py | 32 ------- ...21_notification_model_with_relationship.py | 93 +++++++++++++++++++ .../e21da4ecec18_foreingkey_user_teriff.py | 34 ------- ...reload_association_table_in_group_user_.py | 45 --------- .../versions/fd614408777f_group_user_m2m_4.py | 33 ------- app/models/notification.py | 33 +++++++ app/models/user.py | 8 ++ 14 files changed, 134 insertions(+), 386 deletions(-) delete mode 100644 alembic/versions/16786c7048d6_added_default_role.py delete mode 100644 alembic/versions/41b55aea7689_choice_fix.py delete mode 100644 alembic/versions/5b37c27efb7b_group_user_m2m.py delete mode 100644 alembic/versions/8df1f3e7f3d4_tariff_and_profile_models.py delete mode 100644 alembic/versions/9436e9b65a6a_relationship_user_profile.py delete mode 100644 alembic/versions/bbd832bbe092_group_user_m2m_fix.py delete mode 100644 alembic/versions/ce2a08bc6d33_group_model.py delete mode 100644 alembic/versions/d532edc24b0c_username.py create mode 100644 alembic/versions/da73f8287221_notification_model_with_relationship.py delete mode 100644 alembic/versions/e21da4ecec18_foreingkey_user_teriff.py delete mode 100644 alembic/versions/f195e92a07b3_reload_association_table_in_group_user_.py delete mode 100644 alembic/versions/fd614408777f_group_user_m2m_4.py create mode 100644 app/models/notification.py diff --git a/alembic/versions/16786c7048d6_added_default_role.py b/alembic/versions/16786c7048d6_added_default_role.py deleted file mode 100644 index b95f791..0000000 --- a/alembic/versions/16786c7048d6_added_default_role.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Added default role - -Revision ID: 16786c7048d6 -Revises: 41b55aea7689 -Create Date: 2023-12-08 21:37:55.669358 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '16786c7048d6' -down_revision = '41b55aea7689' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('user', schema=None) as batch_op: - batch_op.alter_column('role', - existing_type=sa.VARCHAR(length=7), - nullable=False) - - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('user', schema=None) as batch_op: - batch_op.alter_column('role', - existing_type=sa.VARCHAR(length=7), - nullable=True) - - # ### end Alembic commands ### diff --git a/alembic/versions/41b55aea7689_choice_fix.py b/alembic/versions/41b55aea7689_choice_fix.py deleted file mode 100644 index ae1ec50..0000000 --- a/alembic/versions/41b55aea7689_choice_fix.py +++ /dev/null @@ -1,32 +0,0 @@ -"""choice fix - -Revision ID: 41b55aea7689 -Revises: 4098f76764d4 -Create Date: 2023-12-08 21:25:27.084367 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '41b55aea7689' -down_revision = '4098f76764d4' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('user', schema=None) as batch_op: - batch_op.add_column(sa.Column('role', sa.Enum('user', 'manager', 'admin', name='userroleenum'), nullable=True)) - - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('user', schema=None) as batch_op: - batch_op.drop_column('role') - - # ### end Alembic commands ### diff --git a/alembic/versions/5b37c27efb7b_group_user_m2m.py b/alembic/versions/5b37c27efb7b_group_user_m2m.py deleted file mode 100644 index 0b9a2fd..0000000 --- a/alembic/versions/5b37c27efb7b_group_user_m2m.py +++ /dev/null @@ -1,28 +0,0 @@ -"""group user m2m - -Revision ID: 5b37c27efb7b -Revises: ce2a08bc6d33 -Create Date: 2023-12-11 02:23:26.166882 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '5b37c27efb7b' -down_revision = 'ce2a08bc6d33' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - pass - # ### end Alembic commands ### diff --git a/alembic/versions/8df1f3e7f3d4_tariff_and_profile_models.py b/alembic/versions/8df1f3e7f3d4_tariff_and_profile_models.py deleted file mode 100644 index 744d2aa..0000000 --- a/alembic/versions/8df1f3e7f3d4_tariff_and_profile_models.py +++ /dev/null @@ -1,42 +0,0 @@ -"""tariff and profile models - -Revision ID: 8df1f3e7f3d4 -Revises: fd614408777f -Create Date: 2023-12-11 21:24:27.374213 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '8df1f3e7f3d4' -down_revision = 'fd614408777f' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('profile', - sa.Column('first_name', sa.String(length=100), nullable=True), - sa.Column('last_name', sa.String(length=100), nullable=True), - sa.Column('age', sa.SmallInteger(), nullable=True), - sa.Column('id', sa.Integer(), nullable=False), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('tariff', - sa.Column('name', sa.String(length=100), nullable=False), - sa.Column('description', sa.Text(), nullable=True), - sa.Column('id', sa.Integer(), nullable=False), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('name') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('tariff') - op.drop_table('profile') - # ### end Alembic commands ### diff --git a/alembic/versions/9436e9b65a6a_relationship_user_profile.py b/alembic/versions/9436e9b65a6a_relationship_user_profile.py deleted file mode 100644 index df9fc72..0000000 --- a/alembic/versions/9436e9b65a6a_relationship_user_profile.py +++ /dev/null @@ -1,36 +0,0 @@ -"""relationship user profile - -Revision ID: 9436e9b65a6a -Revises: e21da4ecec18 -Create Date: 2023-12-11 22:56:24.072674 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '9436e9b65a6a' -down_revision = 'e21da4ecec18' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('profile', schema=None) as batch_op: - batch_op.add_column(sa.Column('user_id', sa.Integer(), nullable=True)) - batch_op.create_unique_constraint('constraint_user', ['user_id']) - batch_op.create_foreign_key('fk_user_profile', 'user', ['user_id'], ['id']) - - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('profile', schema=None) as batch_op: - batch_op.drop_constraint('fk_user_profile', type_='foreignkey') - batch_op.drop_constraint('constraint_user', type_='unique') - batch_op.drop_column('user_id') - - # ### end Alembic commands ### diff --git a/alembic/versions/bbd832bbe092_group_user_m2m_fix.py b/alembic/versions/bbd832bbe092_group_user_m2m_fix.py deleted file mode 100644 index f6a57c4..0000000 --- a/alembic/versions/bbd832bbe092_group_user_m2m_fix.py +++ /dev/null @@ -1,34 +0,0 @@ -"""group user m2m fix - -Revision ID: bbd832bbe092 -Revises: 9436e9b65a6a -Create Date: 2023-12-12 04:32:06.067226 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'bbd832bbe092' -down_revision = '9436e9b65a6a' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('association_table', schema=None) as batch_op: - batch_op.add_column(sa.Column('id', sa.Integer(), nullable=False)) - batch_op.create_unique_constraint('constraint_group_user', ['group_id', 'user_id']) - - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('association_table', schema=None) as batch_op: - batch_op.drop_constraint('constraint_group_user', type_='unique') - batch_op.drop_column('id') - - # ### end Alembic commands ### diff --git a/alembic/versions/ce2a08bc6d33_group_model.py b/alembic/versions/ce2a08bc6d33_group_model.py deleted file mode 100644 index d12bdd9..0000000 --- a/alembic/versions/ce2a08bc6d33_group_model.py +++ /dev/null @@ -1,34 +0,0 @@ -"""group model - -Revision ID: ce2a08bc6d33 -Revises: d532edc24b0c -Create Date: 2023-12-11 00:21:21.862553 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'ce2a08bc6d33' -down_revision = 'd532edc24b0c' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('group', - sa.Column('name', sa.String(length=100), nullable=False), - sa.Column('description', sa.Text(), nullable=True), - sa.Column('id', sa.Integer(), nullable=False), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('name') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('group') - # ### end Alembic commands ### diff --git a/alembic/versions/d532edc24b0c_username.py b/alembic/versions/d532edc24b0c_username.py deleted file mode 100644 index 84733ad..0000000 --- a/alembic/versions/d532edc24b0c_username.py +++ /dev/null @@ -1,32 +0,0 @@ -"""username - -Revision ID: d532edc24b0c -Revises: 16786c7048d6 -Create Date: 2023-12-08 21:50:55.747613 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'd532edc24b0c' -down_revision = '16786c7048d6' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('user', schema=None) as batch_op: - batch_op.add_column(sa.Column('username', sa.String(length=100), nullable=False)) - - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('user', schema=None) as batch_op: - batch_op.drop_column('username') - - # ### end Alembic commands ### diff --git a/alembic/versions/da73f8287221_notification_model_with_relationship.py b/alembic/versions/da73f8287221_notification_model_with_relationship.py new file mode 100644 index 0000000..3dfa826 --- /dev/null +++ b/alembic/versions/da73f8287221_notification_model_with_relationship.py @@ -0,0 +1,93 @@ +"""notification model with relationship + +Revision ID: da73f8287221 +Revises: 4098f76764d4 +Create Date: 2023-12-15 10:55:10.899967 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'da73f8287221' +down_revision = '4098f76764d4' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('group', + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name') + ) + op.create_table('notification', + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name') + ) + op.create_table('tariff', + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name') + ) + op.create_table('group_user_association', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('group_id', sa.Integer(), nullable=True), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['group_id'], ['group.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('group_id', 'user_id', name='constraint_group_user') + ) + op.create_table('notification_user_association', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('notification_id', sa.Integer(), nullable=True), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['notification_id'], ['notification.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('notification_id', 'user_id', name='constraint_notification_user') + ) + op.create_table('profile', + sa.Column('first_name', sa.String(length=100), nullable=True), + sa.Column('last_name', sa.String(length=100), nullable=True), + sa.Column('age', sa.SmallInteger(), nullable=True), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('user_id') + ) + with op.batch_alter_table('user', schema=None) as batch_op: + batch_op.add_column(sa.Column('role', sa.Enum('user', 'manager', 'admin', name='userroleenum'), nullable=False)) + batch_op.add_column(sa.Column('username', sa.String(length=100), nullable=False)) + batch_op.add_column(sa.Column('tariff_id', sa.Integer(), nullable=True)) + batch_op.create_foreign_key('fk_tariff', 'tariff', ['tariff_id'], ['id']) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('user', schema=None) as batch_op: + batch_op.drop_constraint('fk_tariff', type_='foreignkey') + batch_op.drop_column('tariff_id') + batch_op.drop_column('username') + batch_op.drop_column('role') + + op.drop_table('profile') + op.drop_table('notification_user_association') + op.drop_table('group_user_association') + op.drop_table('tariff') + op.drop_table('notification') + op.drop_table('group') + # ### end Alembic commands ### diff --git a/alembic/versions/e21da4ecec18_foreingkey_user_teriff.py b/alembic/versions/e21da4ecec18_foreingkey_user_teriff.py deleted file mode 100644 index c040e1a..0000000 --- a/alembic/versions/e21da4ecec18_foreingkey_user_teriff.py +++ /dev/null @@ -1,34 +0,0 @@ -"""foreingkey user teriff - -Revision ID: e21da4ecec18 -Revises: 8df1f3e7f3d4 -Create Date: 2023-12-11 21:52:01.340149 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'e21da4ecec18' -down_revision = '8df1f3e7f3d4' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('user', schema=None) as batch_op: - batch_op.add_column(sa.Column('tariff_id', sa.Integer(), nullable=True)) - batch_op.create_foreign_key('fk_user_tariff', 'tariff', ['tariff_id'], ['id']) - - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('user', schema=None) as batch_op: - batch_op.drop_constraint(None, type_='foreignkey') - batch_op.drop_column('tariff_id') - - # ### end Alembic commands ### diff --git a/alembic/versions/f195e92a07b3_reload_association_table_in_group_user_.py b/alembic/versions/f195e92a07b3_reload_association_table_in_group_user_.py deleted file mode 100644 index dda6403..0000000 --- a/alembic/versions/f195e92a07b3_reload_association_table_in_group_user_.py +++ /dev/null @@ -1,45 +0,0 @@ -"""reload association_table in group_user_association - -Revision ID: f195e92a07b3 -Revises: bbd832bbe092 -Create Date: 2023-12-12 04:53:44.659421 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'f195e92a07b3' -down_revision = 'bbd832bbe092' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('group_user_association', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('group_id', sa.Integer(), nullable=True), - sa.Column('user_id', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['group_id'], ['group.id'], ), - sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('group_id', 'user_id', name='constraint_group_user') - ) - op.drop_table('association_table') - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('association_table', - sa.Column('group_id', sa.INTEGER(), nullable=True), - sa.Column('user_id', sa.INTEGER(), nullable=True), - sa.Column('id', sa.INTEGER(), nullable=False), - sa.ForeignKeyConstraint(['group_id'], ['group.id'], ), - sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), - sa.UniqueConstraint('group_id', 'user_id', name='constraint_group_user') - ) - op.drop_table('group_user_association') - # ### end Alembic commands ### diff --git a/alembic/versions/fd614408777f_group_user_m2m_4.py b/alembic/versions/fd614408777f_group_user_m2m_4.py deleted file mode 100644 index ebe60ff..0000000 --- a/alembic/versions/fd614408777f_group_user_m2m_4.py +++ /dev/null @@ -1,33 +0,0 @@ -"""group user m2m 4 - -Revision ID: fd614408777f -Revises: 5b37c27efb7b -Create Date: 2023-12-11 02:29:57.140105 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'fd614408777f' -down_revision = '5b37c27efb7b' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('association_table', - sa.Column('group_id', sa.Integer(), nullable=True), - sa.Column('user_id', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['group_id'], ['group.id'], ), - sa.ForeignKeyConstraint(['user_id'], ['user.id'], ) - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('association_table') - # ### end Alembic commands ### diff --git a/app/models/notification.py b/app/models/notification.py new file mode 100644 index 0000000..ac56076 --- /dev/null +++ b/app/models/notification.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from sqlalchemy import (Column, String, Text, Table, ForeignKey, Integer, + UniqueConstraint) +from sqlalchemy.orm import Mapped, relationship + +from app.core.db import Base +from app.core.config import settings + +if TYPE_CHECKING: + from app.models import User + + +notification_user_association = Table( + 'notification_user_association', Base.metadata, + Column('id', Integer, primary_key=True), + Column('notification_id', ForeignKey('notification.id')), + Column('user_id', ForeignKey('user.id')), + UniqueConstraint('notification_id', 'user_id', + name='constraint_notification_user') +) + + +class Notification(Base): + name: str = Column(String(length=settings.max_length_string), unique=True, + nullable=False) + description: str = Column(Text) + users: Mapped[list[User]] = relationship( + secondary=notification_user_association, + back_populates='notifications' + ) diff --git a/app/models/user.py b/app/models/user.py index 91b95ff..9e93ffc 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import enum from typing import TYPE_CHECKING @@ -7,11 +9,13 @@ from app.core.db import Base from .group import group_user_association +from .notification import notification_user_association if TYPE_CHECKING: from .tariff import Tariff from .profile import Profile from .group import Group + from .notification import Notification class UserRoleEnum(enum.Enum): @@ -32,3 +36,7 @@ class User(SQLAlchemyBaseUserTable[int], Base): profile: Mapped['Profile'] = relationship(back_populates='user') groups: Mapped['Group'] = relationship(secondary=group_user_association, back_populates='users') + notifications: Mapped[Notification] = relationship( + secondary=notification_user_association, + back_populates='users' + ) From c6c739aab951057086db9b54d110454d1177a3c0 Mon Sep 17 00:00:00 2001 From: Dmitry Abramov Date: Fri, 15 Dec 2023 11:31:58 +0700 Subject: [PATCH 11/20] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D1=8D=D0=BD=D0=B4=D0=BF=D0=BE=D0=B8=D0=BD=D1=82=D1=8B=20Notifi?= =?UTF-8?q?cation=20create,=20get=5Fmulti?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/endpoints/__init__.py | 1 + app/api/endpoints/notification.py | 24 ++++++++++++++++++++++++ app/api/routers.py | 5 ++++- app/crud/__init__.py | 1 + app/crud/notification.py | 9 +++++++++ app/models/__init__.py | 1 + app/schemas/notification.py | 17 +++++++++++++++++ 7 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 app/api/endpoints/notification.py create mode 100644 app/crud/notification.py create mode 100644 app/schemas/notification.py diff --git a/app/api/endpoints/__init__.py b/app/api/endpoints/__init__.py index f7b3684..d224067 100644 --- a/app/api/endpoints/__init__.py +++ b/app/api/endpoints/__init__.py @@ -2,3 +2,4 @@ from .group import router as group_router # noqa from .profile import router as profile_router # noqa from .tariff import router as tariff_router # noqa +from .notification import router as notification_router # noqa diff --git a/app/api/endpoints/notification.py b/app/api/endpoints/notification.py new file mode 100644 index 0000000..1b7af1b --- /dev/null +++ b/app/api/endpoints/notification.py @@ -0,0 +1,24 @@ +from fastapi import APIRouter, Depends +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.db import get_async_session +from app.crud import notification_crud +from app.schemas.notification import NotificationCreate, NotificationRead + + +router = APIRouter() + + +@router.post('/') +async def create_notification( + notification: NotificationCreate, + session: AsyncSession = Depends(get_async_session) +): + return await notification_crud.create(notification, session) + + +@router.get('/', response_model=list[NotificationRead]) +async def get_all_notifications( + session: AsyncSession = Depends(get_async_session) +): + return await notification_crud.get_multi(session) diff --git a/app/api/routers.py b/app/api/routers.py index 00c0306..58d31ba 100644 --- a/app/api/routers.py +++ b/app/api/routers.py @@ -1,7 +1,8 @@ from fastapi import APIRouter from app.api.endpoints import ( - user_router, group_router, profile_router, tariff_router + user_router, group_router, profile_router, tariff_router, + notification_router, ) main_router = APIRouter() @@ -11,3 +12,5 @@ main_router.include_router(profile_router, prefix='/profiles', tags=['Profile']) main_router.include_router(tariff_router, prefix='/tariffs', tags=['Tariff']) +main_router.include_router(notification_router, prefix='/notifications', + tags=['Notification']) diff --git a/app/crud/__init__.py b/app/crud/__init__.py index 5e11242..27b836a 100644 --- a/app/crud/__init__.py +++ b/app/crud/__init__.py @@ -2,3 +2,4 @@ from .user import user_crud # noqa from .profile import profile_crud # noqa from .tariff import tariff_crud # noqa +from .notification import notification_crud # noqa diff --git a/app/crud/notification.py b/app/crud/notification.py new file mode 100644 index 0000000..f80600e --- /dev/null +++ b/app/crud/notification.py @@ -0,0 +1,9 @@ +from app.crud.base import CRUDBase +from app.models import Notification + + +class CRUDNotification(CRUDBase): + pass + + +notification_crud = CRUDNotification(Notification) diff --git a/app/models/__init__.py b/app/models/__init__.py index 1552c92..baa3ef1 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -2,3 +2,4 @@ from .group import Group # noqa from .profile import Profile # noqa from .tariff import Tariff # noqa +from .notification import Notification # noqa diff --git a/app/schemas/notification.py b/app/schemas/notification.py new file mode 100644 index 0000000..8ead90f --- /dev/null +++ b/app/schemas/notification.py @@ -0,0 +1,17 @@ +from typing import Optional + +from pydantic import BaseModel + + +class NotificationCreate(BaseModel): + name: str + description: Optional[str] + + +class NotificationRead(BaseModel): + id: int + name: Optional[str] + description: Optional[str] + + class Config: + from_attributes = True From 7bad3922ec37ed151e306554a60921ac808a0824 Mon Sep 17 00:00:00 2001 From: Dmitry Abramov Date: Fri, 15 Dec 2023 11:57:30 +0700 Subject: [PATCH 12/20] examination model with relationship, scheme, crud, endpoints --- ...be_examination_model_with_relationship_.py | 44 +++++++++++++++++++ app/api/endpoints/__init__.py | 1 + app/api/endpoints/examination.py | 24 ++++++++++ app/api/routers.py | 4 +- app/crud/__init__.py | 1 + app/crud/examination.py | 9 ++++ app/models/__init__.py | 1 + app/models/examination.py | 33 ++++++++++++++ app/models/user.py | 16 +++++-- app/schemas/examination.py | 17 +++++++ 10 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 alembic/versions/043420d3cabe_examination_model_with_relationship_.py create mode 100644 app/api/endpoints/examination.py create mode 100644 app/crud/examination.py create mode 100644 app/models/examination.py create mode 100644 app/schemas/examination.py diff --git a/alembic/versions/043420d3cabe_examination_model_with_relationship_.py b/alembic/versions/043420d3cabe_examination_model_with_relationship_.py new file mode 100644 index 0000000..a04f7f6 --- /dev/null +++ b/alembic/versions/043420d3cabe_examination_model_with_relationship_.py @@ -0,0 +1,44 @@ +"""examination model with relationship, scheme, crud, endpoints + +Revision ID: 043420d3cabe +Revises: da73f8287221 +Create Date: 2023-12-15 11:49:39.453458 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '043420d3cabe' +down_revision = 'da73f8287221' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('examination', + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name') + ) + op.create_table('examination_user_association', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('examination_id', sa.Integer(), nullable=True), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['examination_id'], ['examination.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('examination_id', 'user_id', name='constraint_examination_user') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('examination_user_association') + op.drop_table('examination') + # ### end Alembic commands ### diff --git a/app/api/endpoints/__init__.py b/app/api/endpoints/__init__.py index d224067..63f3a87 100644 --- a/app/api/endpoints/__init__.py +++ b/app/api/endpoints/__init__.py @@ -3,3 +3,4 @@ from .profile import router as profile_router # noqa from .tariff import router as tariff_router # noqa from .notification import router as notification_router # noqa +from .examination import router as examination_router # noqa diff --git a/app/api/endpoints/examination.py b/app/api/endpoints/examination.py new file mode 100644 index 0000000..de42e5c --- /dev/null +++ b/app/api/endpoints/examination.py @@ -0,0 +1,24 @@ +from fastapi import APIRouter, Depends +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.db import get_async_session +from app.crud import examination_crud +from app.schemas.examination import ExaminationCreate, ExaminationRead + + +router = APIRouter() + + +@router.post('/') +async def create_examination( + examination: ExaminationCreate, + session: AsyncSession = Depends(get_async_session) +): + return await examination_crud.create(examination, session) + + +@router.get('/', response_model=list[ExaminationRead]) +async def get_all_notifications( + session: AsyncSession = Depends(get_async_session) +): + return await examination_crud.get_multi(session) diff --git a/app/api/routers.py b/app/api/routers.py index 58d31ba..0d40906 100644 --- a/app/api/routers.py +++ b/app/api/routers.py @@ -2,7 +2,7 @@ from app.api.endpoints import ( user_router, group_router, profile_router, tariff_router, - notification_router, + notification_router, examination_router ) main_router = APIRouter() @@ -14,3 +14,5 @@ main_router.include_router(tariff_router, prefix='/tariffs', tags=['Tariff']) main_router.include_router(notification_router, prefix='/notifications', tags=['Notification']) +main_router.include_router(examination_router, prefix='/examinations', + tags=['Examination']) diff --git a/app/crud/__init__.py b/app/crud/__init__.py index 27b836a..e7d52f2 100644 --- a/app/crud/__init__.py +++ b/app/crud/__init__.py @@ -3,3 +3,4 @@ from .profile import profile_crud # noqa from .tariff import tariff_crud # noqa from .notification import notification_crud # noqa +from .examination import examination_crud # noqa diff --git a/app/crud/examination.py b/app/crud/examination.py new file mode 100644 index 0000000..b922918 --- /dev/null +++ b/app/crud/examination.py @@ -0,0 +1,9 @@ +from app.crud.base import CRUDBase +from app.models import Examination + + +class CRUDExamination(CRUDBase): + pass + + +examination_crud = CRUDExamination(Examination) diff --git a/app/models/__init__.py b/app/models/__init__.py index baa3ef1..345b017 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -3,3 +3,4 @@ from .profile import Profile # noqa from .tariff import Tariff # noqa from .notification import Notification # noqa +from .examination import Examination # noqa diff --git a/app/models/examination.py b/app/models/examination.py new file mode 100644 index 0000000..c1406cf --- /dev/null +++ b/app/models/examination.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from sqlalchemy import (Column, String, Text, Table, ForeignKey, Integer, + UniqueConstraint) +from sqlalchemy.orm import Mapped, relationship + +from app.core.db import Base +from app.core.config import settings + +if TYPE_CHECKING: + from app.models import User + + +examination_user_association = Table( + 'examination_user_association', Base.metadata, + Column('id', Integer, primary_key=True), + Column('examination_id', ForeignKey('examination.id')), + Column('user_id', ForeignKey('user.id')), + UniqueConstraint('examination_id', 'user_id', + name='constraint_examination_user') +) + + +class Examination(Base): + name: str = Column(String(length=settings.max_length_string), unique=True, + nullable=False) + description: str = Column(Text) + users: Mapped[list[User]] = relationship( + secondary=examination_user_association, + back_populates='examinations' + ) diff --git a/app/models/user.py b/app/models/user.py index 9e93ffc..08a6ded 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -10,12 +10,14 @@ from app.core.db import Base from .group import group_user_association from .notification import notification_user_association +from .examination import examination_user_association if TYPE_CHECKING: from .tariff import Tariff from .profile import Profile from .group import Group from .notification import Notification + from .examination import Examination class UserRoleEnum(enum.Enum): @@ -32,11 +34,17 @@ class User(SQLAlchemyBaseUserTable[int], Base): tariff_id: Mapped[int] = Column( ForeignKey('tariff.id'), ) - tariff: Mapped['Tariff'] = relationship(back_populates='users') - profile: Mapped['Profile'] = relationship(back_populates='user') - groups: Mapped['Group'] = relationship(secondary=group_user_association, - back_populates='users') + tariff: Mapped[Tariff] = relationship(back_populates='users') + profile: Mapped[Profile] = relationship(back_populates='user') + groups: Mapped[Group] = relationship( + secondary=group_user_association, + back_populates='users' + ) notifications: Mapped[Notification] = relationship( secondary=notification_user_association, back_populates='users' ) + examinations: Mapped[Examination] = relationship( + secondary=examination_user_association, + back_populates='users' + ) diff --git a/app/schemas/examination.py b/app/schemas/examination.py new file mode 100644 index 0000000..cf03a9d --- /dev/null +++ b/app/schemas/examination.py @@ -0,0 +1,17 @@ +from typing import Optional + +from pydantic import BaseModel + + +class ExaminationCreate(BaseModel): + name: str + description: Optional[str] + + +class ExaminationRead(BaseModel): + id: int + name: Optional[str] + description: Optional[str] + + class Config: + from_attributes = True From 916e7135392c650a108d98ae693da502b2e4bdb0 Mon Sep 17 00:00:00 2001 From: Dmitry Abramov Date: Fri, 15 Dec 2023 12:17:15 +0700 Subject: [PATCH 13/20] achievement model with relationship, scheme, crud, endpoints --- ...806_achievement_model_with_relationship.py | 44 +++++++++++++++++++ app/api/endpoints/__init__.py | 1 + app/api/endpoints/achievement.py | 24 ++++++++++ app/api/routers.py | 4 +- app/crud/__init__.py | 1 + app/crud/achievement.py | 9 ++++ app/models/__init__.py | 1 + app/models/achievement.py | 33 ++++++++++++++ app/models/profile.py | 10 ++++- app/schemas/achievement.py | 17 +++++++ 10 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 alembic/versions/3a82c343b806_achievement_model_with_relationship.py create mode 100644 app/api/endpoints/achievement.py create mode 100644 app/crud/achievement.py create mode 100644 app/models/achievement.py create mode 100644 app/schemas/achievement.py diff --git a/alembic/versions/3a82c343b806_achievement_model_with_relationship.py b/alembic/versions/3a82c343b806_achievement_model_with_relationship.py new file mode 100644 index 0000000..8a99067 --- /dev/null +++ b/alembic/versions/3a82c343b806_achievement_model_with_relationship.py @@ -0,0 +1,44 @@ +"""achievement model with relationship + +Revision ID: 3a82c343b806 +Revises: 043420d3cabe +Create Date: 2023-12-15 12:15:12.153616 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '3a82c343b806' +down_revision = '043420d3cabe' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('achievement', + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name') + ) + op.create_table('achievement_profile_association', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('achievement_id', sa.Integer(), nullable=True), + sa.Column('profile_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['achievement_id'], ['achievement.id'], ), + sa.ForeignKeyConstraint(['profile_id'], ['profile.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('achievement_id', 'profile_id', name='constraint_achievement_profile') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('achievement_profile_association') + op.drop_table('achievement') + # ### end Alembic commands ### diff --git a/app/api/endpoints/__init__.py b/app/api/endpoints/__init__.py index 63f3a87..6723004 100644 --- a/app/api/endpoints/__init__.py +++ b/app/api/endpoints/__init__.py @@ -4,3 +4,4 @@ from .tariff import router as tariff_router # noqa from .notification import router as notification_router # noqa from .examination import router as examination_router # noqa +from .achievement import router as achievement_router # noqa diff --git a/app/api/endpoints/achievement.py b/app/api/endpoints/achievement.py new file mode 100644 index 0000000..d2c2a47 --- /dev/null +++ b/app/api/endpoints/achievement.py @@ -0,0 +1,24 @@ +from fastapi import APIRouter, Depends +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.db import get_async_session +from app.crud import achievement_crud +from app.schemas.achievement import AchievementCreate, AchievementRead + + +router = APIRouter() + + +@router.post('/') +async def create_achievement( + achievement: AchievementCreate, + session: AsyncSession = Depends(get_async_session) +): + return await achievement_crud.create(achievement, session) + + +@router.get('/', response_model=list[AchievementRead]) +async def get_all_achievements( + session: AsyncSession = Depends(get_async_session) +): + return await achievement_crud.get_multi(session) diff --git a/app/api/routers.py b/app/api/routers.py index 0d40906..dfd0c89 100644 --- a/app/api/routers.py +++ b/app/api/routers.py @@ -2,7 +2,7 @@ from app.api.endpoints import ( user_router, group_router, profile_router, tariff_router, - notification_router, examination_router + notification_router, examination_router, achievement_router ) main_router = APIRouter() @@ -16,3 +16,5 @@ tags=['Notification']) main_router.include_router(examination_router, prefix='/examinations', tags=['Examination']) +main_router.include_router(achievement_router, prefix='/achievements', + tags=['Achievement']) diff --git a/app/crud/__init__.py b/app/crud/__init__.py index e7d52f2..bbc9c3d 100644 --- a/app/crud/__init__.py +++ b/app/crud/__init__.py @@ -4,3 +4,4 @@ from .tariff import tariff_crud # noqa from .notification import notification_crud # noqa from .examination import examination_crud # noqa +from .achievement import achievement_crud # noqa diff --git a/app/crud/achievement.py b/app/crud/achievement.py new file mode 100644 index 0000000..ec44edc --- /dev/null +++ b/app/crud/achievement.py @@ -0,0 +1,9 @@ +from app.crud.base import CRUDBase +from app.models import Achievement + + +class CRUDAchievement(CRUDBase): + pass + + +achievement_crud = CRUDAchievement(Achievement) diff --git a/app/models/__init__.py b/app/models/__init__.py index 345b017..af3d8ab 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -4,3 +4,4 @@ from .tariff import Tariff # noqa from .notification import Notification # noqa from .examination import Examination # noqa +from .achievement import Achievement # noqa diff --git a/app/models/achievement.py b/app/models/achievement.py new file mode 100644 index 0000000..f9a0461 --- /dev/null +++ b/app/models/achievement.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from sqlalchemy import (Column, String, Text, Table, ForeignKey, Integer, + UniqueConstraint) +from sqlalchemy.orm import Mapped, relationship + +from app.core.db import Base +from app.core.config import settings + +if TYPE_CHECKING: + from app.models import Profile + + +achievement_profile_association = Table( + 'achievement_profile_association', Base.metadata, + Column('id', Integer, primary_key=True), + Column('achievement_id', ForeignKey('achievement.id')), + Column('profile_id', ForeignKey('profile.id')), + UniqueConstraint('achievement_id', 'profile_id', + name='constraint_achievement_profile') +) + + +class Achievement(Base): + name: str = Column(String(length=settings.max_length_string), unique=True, + nullable=False) + description: str = Column(Text) + profiles: Mapped[list[Profile]] = relationship( + secondary=achievement_profile_association, + back_populates='achievements' + ) diff --git a/app/models/profile.py b/app/models/profile.py index 480ef07..6f57822 100644 --- a/app/models/profile.py +++ b/app/models/profile.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import TYPE_CHECKING from sqlalchemy import Column, String, SmallInteger, ForeignKey @@ -5,9 +7,11 @@ from app.core.db import Base from app.core.config import settings +from .achievement import achievement_profile_association if TYPE_CHECKING: from .user import User + from .achievement import Achievement class Profile(Base): @@ -17,4 +21,8 @@ class Profile(Base): user_id: Mapped[int] = Column( ForeignKey('user.id'), unique=True ) - user: Mapped['User'] = relationship(back_populates='profile') + user: Mapped[User] = relationship(back_populates='profile') + achievements: Mapped[Achievement] = relationship( + secondary=achievement_profile_association, + back_populates='profiles' + ) diff --git a/app/schemas/achievement.py b/app/schemas/achievement.py new file mode 100644 index 0000000..bc47044 --- /dev/null +++ b/app/schemas/achievement.py @@ -0,0 +1,17 @@ +from typing import Optional + +from pydantic import BaseModel + + +class AchievementCreate(BaseModel): + name: str + description: Optional[str] + + +class AchievementRead(BaseModel): + id: int + name: Optional[str] + description: Optional[str] + + class Config: + from_attributes = True From 5f1f6ae271f0263b031835bd872005b9aa4095af Mon Sep 17 00:00:00 2001 From: Dmitry Abramov Date: Fri, 15 Dec 2023 18:53:05 +0700 Subject: [PATCH 14/20] course model, course user relationship --- .../b6230f9d49dc_course_user_relationship.py | 44 +++++++++++++++++++ app/models/course.py | 33 ++++++++++++++ app/models/user.py | 6 +++ 3 files changed, 83 insertions(+) create mode 100644 alembic/versions/b6230f9d49dc_course_user_relationship.py create mode 100644 app/models/course.py diff --git a/alembic/versions/b6230f9d49dc_course_user_relationship.py b/alembic/versions/b6230f9d49dc_course_user_relationship.py new file mode 100644 index 0000000..2c6ed27 --- /dev/null +++ b/alembic/versions/b6230f9d49dc_course_user_relationship.py @@ -0,0 +1,44 @@ +"""course user relationship + +Revision ID: b6230f9d49dc +Revises: 3a82c343b806 +Create Date: 2023-12-15 18:51:03.288013 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'b6230f9d49dc' +down_revision = '3a82c343b806' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('course', + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name') + ) + op.create_table('course_user_association', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('course_id', sa.Integer(), nullable=True), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['course_id'], ['course.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('course_id', 'user_id', name='constraint_course_user') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('course_user_association') + op.drop_table('course') + # ### end Alembic commands ### diff --git a/app/models/course.py b/app/models/course.py new file mode 100644 index 0000000..48220a9 --- /dev/null +++ b/app/models/course.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from sqlalchemy import (Column, String, Text, Table, ForeignKey, Integer, + UniqueConstraint) +from sqlalchemy.orm import Mapped, relationship + +from app.core.db import Base +from app.core.config import settings + +if TYPE_CHECKING: + from app.models import User + + +course_user_association = Table( + 'course_user_association', Base.metadata, + Column('id', Integer, primary_key=True), + Column('course_id', ForeignKey('course.id')), + Column('user_id', ForeignKey('user.id')), + UniqueConstraint('course_id', 'user_id', + name='constraint_course_user') +) + + +class Course(Base): + name: str = Column(String(length=settings.max_length_string), unique=True, + nullable=False) + description: str = Column(Text) + users: Mapped[list[User]] = relationship( + secondary=course_user_association, + back_populates='courses' + ) diff --git a/app/models/user.py b/app/models/user.py index 08a6ded..c2fe8f6 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -11,6 +11,7 @@ from .group import group_user_association from .notification import notification_user_association from .examination import examination_user_association +from .course import course_user_association if TYPE_CHECKING: from .tariff import Tariff @@ -18,6 +19,7 @@ from .group import Group from .notification import Notification from .examination import Examination + from .course import Course class UserRoleEnum(enum.Enum): @@ -48,3 +50,7 @@ class User(SQLAlchemyBaseUserTable[int], Base): secondary=examination_user_association, back_populates='users' ) + courses: Mapped[Course] = relationship( + secondary=course_user_association, + back_populates='users' + ) From c74b596475a99e8111b283a468e9df8a31c88a96 Mon Sep 17 00:00:00 2001 From: Dmitry Abramov Date: Fri, 15 Dec 2023 19:15:43 +0700 Subject: [PATCH 15/20] course tariff relationship --- ...12ebaaa6044c_course_tariff_relationship.py | 36 +++++++++++++++++++ app/models/course.py | 16 ++++++++- app/models/tariff.py | 10 +++++- 3 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 alembic/versions/12ebaaa6044c_course_tariff_relationship.py diff --git a/alembic/versions/12ebaaa6044c_course_tariff_relationship.py b/alembic/versions/12ebaaa6044c_course_tariff_relationship.py new file mode 100644 index 0000000..9da41f0 --- /dev/null +++ b/alembic/versions/12ebaaa6044c_course_tariff_relationship.py @@ -0,0 +1,36 @@ +"""course tariff relationship + +Revision ID: 12ebaaa6044c +Revises: b6230f9d49dc +Create Date: 2023-12-15 19:02:40.113127 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '12ebaaa6044c' +down_revision = 'b6230f9d49dc' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('course_tariff_association', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('course_id', sa.Integer(), nullable=True), + sa.Column('tariff_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['course_id'], ['course.id'], ), + sa.ForeignKeyConstraint(['tariff_id'], ['tariff.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('course_id', 'tariff_id', name='constraint_course_tariff') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('course_tariff_association') + # ### end Alembic commands ### diff --git a/app/models/course.py b/app/models/course.py index 48220a9..cfb8463 100644 --- a/app/models/course.py +++ b/app/models/course.py @@ -10,7 +10,8 @@ from app.core.config import settings if TYPE_CHECKING: - from app.models import User + from .user import User + from .tariff import Tariff course_user_association = Table( @@ -22,6 +23,15 @@ name='constraint_course_user') ) +course_tariff_association = Table( + 'course_tariff_association', Base.metadata, + Column('id', Integer, primary_key=True), + Column('course_id', ForeignKey('course.id')), + Column('tariff_id', ForeignKey('tariff.id')), + UniqueConstraint('course_id', 'tariff_id', + name='constraint_course_tariff') +) + class Course(Base): name: str = Column(String(length=settings.max_length_string), unique=True, @@ -31,3 +41,7 @@ class Course(Base): secondary=course_user_association, back_populates='courses' ) + tariffs: Mapped[list[Tariff]] = relationship( + secondary=course_tariff_association, + back_populates='courses' + ) diff --git a/app/models/tariff.py b/app/models/tariff.py index 4396c3e..3a2e7fd 100644 --- a/app/models/tariff.py +++ b/app/models/tariff.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import TYPE_CHECKING from sqlalchemy import Column, String, Text @@ -5,13 +7,19 @@ from app.core.db import Base from app.core.config import settings +from .course import course_tariff_association if TYPE_CHECKING: from .user import User + from .course import Course class Tariff(Base): name: Mapped[str] = Column(String(length=settings.max_length_string), unique=True, nullable=False) description: Mapped[str] = Column(Text) - users: Mapped[list['User'] | None] = relationship(back_populates='tariff') + users: Mapped[list[User]] = relationship(back_populates='tariff') + courses: Mapped[list[Course]] = relationship( + secondary=course_tariff_association, + back_populates='tariffs' + ) From e47a2516eeb651402f07152a790e56e1e0362833 Mon Sep 17 00:00:00 2001 From: Dmitry Abramov Date: Fri, 15 Dec 2023 19:24:29 +0700 Subject: [PATCH 16/20] task model, course task relationship --- .../d1e8da8e858b_course_task_relationship.py | 44 +++++++++++++++++++ app/models/course.py | 7 +++ app/models/task.py | 33 ++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 alembic/versions/d1e8da8e858b_course_task_relationship.py create mode 100644 app/models/task.py diff --git a/alembic/versions/d1e8da8e858b_course_task_relationship.py b/alembic/versions/d1e8da8e858b_course_task_relationship.py new file mode 100644 index 0000000..b8a20dc --- /dev/null +++ b/alembic/versions/d1e8da8e858b_course_task_relationship.py @@ -0,0 +1,44 @@ +"""course task relationship + +Revision ID: d1e8da8e858b +Revises: 12ebaaa6044c +Create Date: 2023-12-15 19:21:08.180548 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'd1e8da8e858b' +down_revision = '12ebaaa6044c' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('task', + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('id', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name') + ) + op.create_table('task_course_association', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('task_id', sa.Integer(), nullable=True), + sa.Column('course_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['course_id'], ['course.id'], ), + sa.ForeignKeyConstraint(['task_id'], ['task.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('task_id', 'course_id', name='constraint_task_course') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('task_course_association') + op.drop_table('task') + # ### end Alembic commands ### diff --git a/app/models/course.py b/app/models/course.py index cfb8463..d2c0615 100644 --- a/app/models/course.py +++ b/app/models/course.py @@ -9,9 +9,12 @@ from app.core.db import Base from app.core.config import settings +from .task import task_course_association + if TYPE_CHECKING: from .user import User from .tariff import Tariff + from .task import Task course_user_association = Table( @@ -45,3 +48,7 @@ class Course(Base): secondary=course_tariff_association, back_populates='courses' ) + tasks: Mapped[list[Task]] = relationship( + secondary=task_course_association, + back_populates='courses' + ) diff --git a/app/models/task.py b/app/models/task.py new file mode 100644 index 0000000..930fd90 --- /dev/null +++ b/app/models/task.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from sqlalchemy import (Column, String, Text, Table, ForeignKey, Integer, + UniqueConstraint) +from sqlalchemy.orm import Mapped, relationship + +from app.core.db import Base +from app.core.config import settings + +if TYPE_CHECKING: + from .user import Course + + +task_course_association = Table( + 'task_course_association', Base.metadata, + Column('id', Integer, primary_key=True), + Column('task_id', ForeignKey('task.id')), + Column('course_id', ForeignKey('course.id')), + UniqueConstraint('task_id', 'course_id', + name='constraint_task_course') +) + + +class Task(Base): + name: str = Column(String(length=settings.max_length_string), unique=True, + nullable=False) + description: str = Column(Text) + courses: Mapped[list[Course]] = relationship( + secondary=task_course_association, + back_populates='tasks' + ) From 4d08a0aa702674f077a469b229e6f5b0da84dfc5 Mon Sep 17 00:00:00 2001 From: Dmitry Abramov Date: Fri, 15 Dec 2023 19:47:00 +0700 Subject: [PATCH 17/20] endpoints create and get_multi for course and task --- app/api/endpoints/__init__.py | 2 ++ app/api/endpoints/course.py | 24 ++++++++++++++++++++++++ app/api/endpoints/task.py | 24 ++++++++++++++++++++++++ app/api/routers.py | 6 +++++- app/crud/__init__.py | 2 ++ app/crud/course.py | 9 +++++++++ app/crud/task.py | 9 +++++++++ app/models/__init__.py | 2 ++ app/schemas/course.py | 17 +++++++++++++++++ app/schemas/task.py | 17 +++++++++++++++++ 10 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 app/api/endpoints/course.py create mode 100644 app/api/endpoints/task.py create mode 100644 app/crud/course.py create mode 100644 app/crud/task.py create mode 100644 app/schemas/course.py create mode 100644 app/schemas/task.py diff --git a/app/api/endpoints/__init__.py b/app/api/endpoints/__init__.py index 6723004..1041bbb 100644 --- a/app/api/endpoints/__init__.py +++ b/app/api/endpoints/__init__.py @@ -5,3 +5,5 @@ from .notification import router as notification_router # noqa from .examination import router as examination_router # noqa from .achievement import router as achievement_router # noqa +from .course import router as course_router # noqa +from .task import router as task_router # noqa diff --git a/app/api/endpoints/course.py b/app/api/endpoints/course.py new file mode 100644 index 0000000..3f679ae --- /dev/null +++ b/app/api/endpoints/course.py @@ -0,0 +1,24 @@ +from fastapi import APIRouter, Depends +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.db import get_async_session +from app.crud import course_crud +from app.schemas.course import CourseCreate, CourseRead + + +router = APIRouter() + + +@router.post('/') +async def create_course( + course: CourseCreate, + session: AsyncSession = Depends(get_async_session) +): + return await course_crud.create(course, session) + + +@router.get('/', response_model=list[CourseRead]) +async def get_all_courses( + session: AsyncSession = Depends(get_async_session) +): + return await course_crud.get_multi(session) diff --git a/app/api/endpoints/task.py b/app/api/endpoints/task.py new file mode 100644 index 0000000..74cd605 --- /dev/null +++ b/app/api/endpoints/task.py @@ -0,0 +1,24 @@ +from fastapi import APIRouter, Depends +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.db import get_async_session +from app.crud import task_crud +from app.schemas.task import TaskCreate, TaskRead + + +router = APIRouter() + + +@router.post('/') +async def create_task( + task: TaskCreate, + session: AsyncSession = Depends(get_async_session) +): + return await task_crud.create(task, session) + + +@router.get('/', response_model=list[TaskRead]) +async def get_all_tasks( + session: AsyncSession = Depends(get_async_session) +): + return await task_crud.get_multi(session) diff --git a/app/api/routers.py b/app/api/routers.py index dfd0c89..f4de3a8 100644 --- a/app/api/routers.py +++ b/app/api/routers.py @@ -2,7 +2,8 @@ from app.api.endpoints import ( user_router, group_router, profile_router, tariff_router, - notification_router, examination_router, achievement_router + notification_router, examination_router, achievement_router, + course_router, task_router ) main_router = APIRouter() @@ -18,3 +19,6 @@ tags=['Examination']) main_router.include_router(achievement_router, prefix='/achievements', tags=['Achievement']) +main_router.include_router(course_router, prefix='/courses', + tags=['Course']) +main_router.include_router(task_router, prefix='/tasks', tags=['Task']) diff --git a/app/crud/__init__.py b/app/crud/__init__.py index bbc9c3d..ab6b702 100644 --- a/app/crud/__init__.py +++ b/app/crud/__init__.py @@ -5,3 +5,5 @@ from .notification import notification_crud # noqa from .examination import examination_crud # noqa from .achievement import achievement_crud # noqa +from .course import course_crud # noqa +from .task import task_crud # noqa diff --git a/app/crud/course.py b/app/crud/course.py new file mode 100644 index 0000000..cad221b --- /dev/null +++ b/app/crud/course.py @@ -0,0 +1,9 @@ +from app.crud.base import CRUDBase +from app.models import Course + + +class CRUDCourse(CRUDBase): + pass + + +course_crud = CRUDCourse(Course) diff --git a/app/crud/task.py b/app/crud/task.py new file mode 100644 index 0000000..18e1746 --- /dev/null +++ b/app/crud/task.py @@ -0,0 +1,9 @@ +from app.crud.base import CRUDBase +from app.models import Task + + +class CRUDTask(CRUDBase): + pass + + +task_crud = CRUDTask(Task) diff --git a/app/models/__init__.py b/app/models/__init__.py index af3d8ab..a4683bd 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -5,3 +5,5 @@ from .notification import Notification # noqa from .examination import Examination # noqa from .achievement import Achievement # noqa +from .course import Course # noqa +from .task import Task # noqa diff --git a/app/schemas/course.py b/app/schemas/course.py new file mode 100644 index 0000000..db822ae --- /dev/null +++ b/app/schemas/course.py @@ -0,0 +1,17 @@ +from typing import Optional + +from pydantic import BaseModel + + +class CourseCreate(BaseModel): + name: str + description: Optional[str] + + +class CourseRead(BaseModel): + id: int + name: Optional[str] + description: Optional[str] + + class Config: + from_attributes = True diff --git a/app/schemas/task.py b/app/schemas/task.py new file mode 100644 index 0000000..f139f91 --- /dev/null +++ b/app/schemas/task.py @@ -0,0 +1,17 @@ +from typing import Optional + +from pydantic import BaseModel + + +class TaskCreate(BaseModel): + name: str + description: Optional[str] + + +class TaskRead(BaseModel): + id: int + name: Optional[str] + description: Optional[str] + + class Config: + from_attributes = True From 9db73fbb8dae339c6976cbaae5881ee3946686ea Mon Sep 17 00:00:00 2001 From: Dmitry Abramov Date: Sat, 16 Dec 2023 22:02:41 +0700 Subject: [PATCH 18/20] pep8 --- app/crud/profile.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/crud/profile.py b/app/crud/profile.py index db8f174..4539cc2 100644 --- a/app/crud/profile.py +++ b/app/crud/profile.py @@ -8,7 +8,8 @@ class CRUDProfile(CRUDBase): async def create( - self, obj_in: ProfileCreate, user_id: int, session: AsyncSession + self, obj_in: ProfileCreate, + user_id: int, session: AsyncSession ): obj_in_data: dict = obj_in.model_dump() obj_in_data['user_id'] = user_id From 85431b9a5a9d5045f8eeb583891c403940ad89b4 Mon Sep 17 00:00:00 2001 From: Dmitry Abramov Date: Sun, 17 Dec 2023 00:32:29 +0700 Subject: [PATCH 19/20] add endpoint delete_group --- app/api/endpoints/group.py | 24 ++++++++++++++++++++---- app/schemas/group.py | 4 ++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/app/api/endpoints/group.py b/app/api/endpoints/group.py index 7592ef4..0d272da 100644 --- a/app/api/endpoints/group.py +++ b/app/api/endpoints/group.py @@ -1,11 +1,12 @@ -from typing import List +from http import HTTPStatus -from fastapi import APIRouter, Depends +from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm.exc import UnmappedInstanceError from app.core.db import get_async_session from app.crud import group_crud -from app.schemas.group import GroupCreate, GroupRead +from app.schemas.group import GroupCreate, GroupRead, GroupRemove router = APIRouter() @@ -19,7 +20,7 @@ async def create_group( return await group_crud.create(group, session=session) -@router.get('/', response_model=List[GroupRead]) +@router.get('/', response_model=list[GroupRead]) async def get_all_groups( session: AsyncSession = Depends(get_async_session) ): @@ -32,3 +33,18 @@ async def get_group( session: AsyncSession = Depends(get_async_session) ): return await group_crud.get(group_id, session=session) + + +@router.delete('/{group_id}') +async def delete_group( + group_id: int, + session: AsyncSession = Depends(get_async_session), +): + try: + db_obj = await group_crud.get(group_id, session) + return await group_crud.remove(db_obj, session) + except UnmappedInstanceError: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, + detail=f'Группа с id {group_id} не найдена.' + ) diff --git a/app/schemas/group.py b/app/schemas/group.py index f1b55d3..d3e8b3f 100644 --- a/app/schemas/group.py +++ b/app/schemas/group.py @@ -15,3 +15,7 @@ class GroupRead(BaseModel): class Config: from_attributes = True + + +class GroupRemove(GroupRead): + id: Optional[int] From 75240455b71297478b68b07f64423bc4e5e49ce4 Mon Sep 17 00:00:00 2001 From: Dmitry Abramov Date: Sun, 17 Dec 2023 00:34:22 +0700 Subject: [PATCH 20/20] add endpoint delete_group --- app/api/endpoints/group.py | 2 +- app/schemas/group.py | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/api/endpoints/group.py b/app/api/endpoints/group.py index 0d272da..9090ba2 100644 --- a/app/api/endpoints/group.py +++ b/app/api/endpoints/group.py @@ -6,7 +6,7 @@ from app.core.db import get_async_session from app.crud import group_crud -from app.schemas.group import GroupCreate, GroupRead, GroupRemove +from app.schemas.group import GroupCreate, GroupRead router = APIRouter() diff --git a/app/schemas/group.py b/app/schemas/group.py index d3e8b3f..f1b55d3 100644 --- a/app/schemas/group.py +++ b/app/schemas/group.py @@ -15,7 +15,3 @@ class GroupRead(BaseModel): class Config: from_attributes = True - - -class GroupRemove(GroupRead): - id: Optional[int]