Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/endpoints group #55

Merged
merged 31 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
884eba7
merge feat/test_auth
Jan 20, 2024
dc1c710
resolve profile.py
Jan 20, 2024
ba91414
tests profile
Jan 20, 2024
fc7629d
fix gitignore, test.fixtures.user
Jan 24, 2024
0b414f2
Фикстура для создания юзеров с профилями гернерит 5 юзеров
Jan 24, 2024
fc9c8e2
тест фильтрации
Jan 24, 2024
a363ac8
тест пагинации
Jan 24, 2024
afae6f4
Тест получения своего профиля
Jan 24, 2024
4481485
тест запрета получения чужого профиля обычным юзером
Jan 24, 2024
6d1e2e3
тест получения фото своего профиля
Jan 24, 2024
2a4c024
вынес получение юзера в отдельную функцию
Jan 24, 2024
24d418e
refactoring
Jan 24, 2024
879aae1
тест апдейта профиля
Jan 25, 2024
9e084c3
Добавил тесты
Jan 25, 2024
ead1366
test udate photo
Jan 28, 2024
5afb149
isort
Jan 28, 2024
0e4842d
gitignore и пустой метод get для получения группы по id
Jan 28, 2024
da5e065
resolved
Jan 28, 2024
35d5af5
добавил тесты на создание группы суперюзером и запрет для юзера
Jan 28, 2024
7a023c4
эндпоинты, круд, тесты
Jan 29, 2024
204482f
Эндпоинты, круд, модели, тесты
Jan 29, 2024
587c5c8
refactoring, isort
Jan 29, 2024
b658d8b
flake8
Jan 29, 2024
3bd713c
isort tests
Jan 29, 2024
fc0098d
рефакторинг
Jan 30, 2024
1988797
resolve
Jan 30, 2024
5bb1292
refactoring
Jan 30, 2024
e8f0910
pep8
Jan 30, 2024
9774e9c
refactoring status codes
Jan 31, 2024
c9a23a7
добавлена пагинация на гет и тест пагинации
Jan 31, 2024
f0eed70
isort
Jan 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
test.db
test.db-journal

.vscode/
*.jpg
*.PNG
infra/.env
test.db

# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down
89 changes: 83 additions & 6 deletions app/api/endpoints/group.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,93 @@
from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, HTTPException, Response, status
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.validators import check_name_duplicate
from app.core.db import get_async_session
from app.core.user import current_superuser, current_user
from app.crud import group_crud
from app.schemas.group import GroupCreate, GroupRead
from app.models import Group, User
from app.schemas.group import GroupCreate, GroupRead, GroupUpdate
from app.services.endpoints_services import delete_obj
from app.services.utils import (Pagination, add_response_headers,
get_pagination_params, paginated)

router = APIRouter()


@router.get(
"/",
response_model=list[GroupRead],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Пагинацию пока не будем добавлять в эти эндпоинты?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

++
и тест на пагинацию добавил

dependencies=[Depends(current_user)]
dependencies=[Depends(current_superuser)]
)
async def get_all_groups(
response: Response,
session: AsyncSession = Depends(get_async_session),
pagination: Pagination = Depends(get_pagination_params)
) -> list[GroupRead]:
"""Возвращает все группы."""
return await group_crud.get_multi(session)
groups = await group_crud.get_multi(session)
response = add_response_headers(
response, groups, pagination
)
return paginated(groups, pagination)


@router.get(
'/me',
response_model=list[GroupRead],
dependencies=[Depends(current_user)]
)
async def get_self_groups(
user: User = Depends(current_user),
session: AsyncSession = Depends(get_async_session)
):
"""Получение групп юзером."""
return await group_crud.get_users_obj(user.id, session)


@router.get(
'/me/{group_id}',
response_model=GroupRead,
dependencies=[Depends(current_user)]
)
async def get_self_group_by_id(
group_id: int,
user: User = Depends(current_user),
session: AsyncSession = Depends(get_async_session)
):
"""Получение группы по id юзером."""
group: Group | None = await group_crud.get(group_id, session)
if group is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='Такой группы не существует.'
)
if user not in group.users:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail='Вы не состоите в этой группе.'
)
return group


@router.get(
'/{group_id}',
response_model=GroupRead,
dependencies=[Depends(current_superuser)]
)
async def get_group(
group_id: int,
session: AsyncSession = Depends(get_async_session)
):
"""Получение группы по id"""
return await group_crud.get(group_id, session)


@router.post(
"/",
response_model=GroupRead,
dependencies=[Depends(current_superuser)]
dependencies=[Depends(current_superuser)],
status_code=status.HTTP_201_CREATED
)
async def create_group(
group: GroupCreate, session: AsyncSession = Depends(get_async_session)
Expand All @@ -36,9 +97,25 @@ async def create_group(
return await group_crud.create(obj_in=group, session=session)


@router.patch(
'/{group_id}',
dependencies=[Depends(current_superuser)],
response_model=GroupRead
)
async def update_group(
group_id: int,
group: GroupUpdate,
session: AsyncSession = Depends(get_async_session)
):
"""Апдейт группы."""
_group = await group_crud.get(group_id, session)
return await group_crud.update(_group, group, session)


@router.delete(
"/{obj_id}",
dependencies=[Depends(current_superuser)]
dependencies=[Depends(current_superuser)],
status_code=status.HTTP_204_NO_CONTENT
)
async def delete_group(
obj_id: int,
Expand Down
18 changes: 12 additions & 6 deletions app/api/endpoints/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,16 @@ async def get_current_user_profile(
)


@router.get(
'/me//photo',
dependencies=[Depends(current_user)]
)
@router.get('/{profile_id}', response_model=ProfileRead,
dependencies=[Depends(current_superuser)])
async def get_profile(
profile_id: int,
session: AsyncSession = Depends(get_async_session)
):
return await profile_crud.get(profile_id, session)


@router.get('/me/photo', dependencies=[Depends(current_user)])
async def get_user_photo(
user: User = Depends(current_user),
session: AsyncSession = Depends(get_async_session)
Expand All @@ -76,7 +82,7 @@ async def update_profile(


@router.patch(
'/me//update_photo',
'/me/update_photo',
response_model=ProfileRead,
dependencies=[Depends(current_user)]
)
Expand Down Expand Up @@ -109,7 +115,7 @@ def create_profile():


@router.delete('/{obj_id}', deprecated=True)
def delete_profile(obg_id: str):
def delete_profile():
"""Удалить объект"""
raise HTTPException(
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
Expand Down
29 changes: 27 additions & 2 deletions app/crud/group.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,34 @@
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from app.crud.base import CRUDBase
from app.models import Group
from app.models import Group, User


class CRUDGroup(CRUDBase):
pass
async def get(self, group_id: int, session: AsyncSession):
stmt = (
select(Group)
.where(Group.id == group_id)
.options(
selectinload(Group.users)
)
)
group = await session.execute(stmt)
group = group.scalars().first()
return group

async def get_users_obj(self, user_id: int, session: AsyncSession):
stmt = (
select(Group)
# .where(Group.users == user_id)
.options(
selectinload(Group.users)
).where(Group.users.any(User.id == user_id))
)
db_obj = await session.execute(stmt)
return db_obj.scalars().all()


group_crud = CRUDGroup(Group)
9 changes: 9 additions & 0 deletions app/crud/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ async def get_multi(self, session: AsyncSession):
)
return db_objs.scalars().all()

async def get(self, obj_id: int, session: AsyncSession):
profile = await session.execute(
select(self.model)
.options(
selectinload(Profile.achievements)
)
)
return profile.scalars().first()

async def get_user_photo(
self,
user_id: int,
Expand Down
4 changes: 3 additions & 1 deletion app/models/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,6 @@ class Profile(Base):
)

def __repr__(self):
return self.first_name
if self.first_name:
return self.first_name
return str(self.user_id)
2 changes: 1 addition & 1 deletion app/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class User(SQLAlchemyBaseUserTable[int], Base):
)
tariff: Mapped[Tariff] = relationship(back_populates="users")
profile: Mapped[Profile] = relationship(back_populates="user")
groups: Mapped[Group] = relationship(
groups: Mapped[list[Group]] = relationship(
secondary=group_user_association, back_populates="users"
)
notifications: Mapped[Notification] = relationship(
Expand Down
7 changes: 6 additions & 1 deletion app/schemas/group.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Optional

from pydantic import BaseModel
from pydantic import BaseModel, Field


class GroupCreate(BaseModel):
Expand All @@ -15,3 +15,8 @@ class GroupRead(BaseModel):

class Config:
from_attributes = True


class GroupUpdate(BaseModel):
name: Optional[str] = Field(None,)
description: Optional[str] = Field(None,)
3 changes: 0 additions & 3 deletions app/schemas/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

from pydantic import BaseModel, Field

from app.schemas.achievement import AchievementRead


class ProfileRead(BaseModel):
id: int
Expand All @@ -12,7 +10,6 @@ class ProfileRead(BaseModel):
age: Optional[int]
user_id: int
image: Optional[str]
achievements: Optional[list[AchievementRead]]

class Config:
from_attributes = True
Expand Down
2 changes: 2 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

pytest_plugins = [
'tests.fixtures.user',
'tests.fixtures.profile',
'tests.fixtures.group'
]

engine_test = create_async_engine(
Expand Down
18 changes: 18 additions & 0 deletions tests/fixtures/group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import pytest_asyncio
from sqlalchemy.ext.asyncio import AsyncSession

from app.models import Group


@pytest_asyncio.fixture
async def moc_groups(
db_session: AsyncSession
) -> None:
moc_groups = [
Group(
name=f'Group_{i}',
description=f'Description for Group_{i}'
) for i in range(1, 6)
]
db_session.add_all(moc_groups)
await db_session.commit()
90 changes: 90 additions & 0 deletions tests/fixtures/profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
from typing import AsyncGenerator

import pytest_asyncio
from passlib.hash import bcrypt
from sqlalchemy import select

from app.models import Profile, User
from tests.conftest import AsyncSessionLocalTest


@pytest_asyncio.fixture
async def moc_users(
db_session: AsyncSessionLocalTest
) -> AsyncGenerator:
"""Фикстура заполнения базы юзерами с профилями."""
hashed_password = bcrypt.hash('qwerty')
moc_users = [
User(
email=f'user_{i}@example.com',
hashed_password=hashed_password,
role='user',
username=f'user_{i}'
) for i in range(1, 6)
]
db_session.add_all(moc_users)
await db_session.commit()
moc_users = await db_session.execute(select(User))
moc_users = moc_users.scalars().all()
moc_profiles = [
Profile(
first_name=f'name_{user.username}',
last_name=f'surname_{user.username}',
age=i + 20,
user_id=user.id
) for i, user in enumerate(moc_users)
]
db_session.add_all(moc_profiles)
await db_session.commit()


# @pytest_asyncio.fixture
# async def user_1(
# prepare_database: FastAPI,
# db_session: AsyncSessionLocalTest
# ) -> AsyncGenerator:
# """Фикстура зарегистрированного клиента."""
# hashed_password = bcrypt.hash('qwerty')
# user_1 = User(
# email='[email protected]',
# hashed_password=hashed_password,
# role='user',
# username='user_1'
# )
# db_session.add(user_1)
# await db_session.commit()
# await db_session.refresh(user_1)
# profile_1 = Profile(
# first_name='user_1_fn',
# last_name='user_1_ln',
# age=25,
# user_id=user_1.id
# )
# db_session.add(profile_1)
# await db_session.commit()


# @pytest_asyncio.fixture
# async def user_2(
# prepare_database: FastAPI,
# db_session: AsyncSessionLocalTest
# ) -> AsyncGenerator:
# """Фикстура зарегистрированного клиента."""
# hashed_password = bcrypt.hash('qwerty')
# user_2 = User(
# email='[email protected]',
# hashed_password=hashed_password,
# role='user',
# username='user_2'
# )
# db_session.add(user_2)
# await db_session.commit()
# await db_session.refresh(user_2)
# profile_2 = Profile(
# first_name='user_2_fn',
# last_name='user_2_ln',
# age=47,
# user_id=user_2.id
# )
# db_session.add(profile_2)
# await db_session.commit()
Loading
Loading