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/test achievements #56

Closed
wants to merge 49 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 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
4a55710
test create achievements by supruser, fix status code in response
Jan 29, 2024
bca4e5f
тест создания ачивмент с пропущенными данными
Jan 29, 2024
b504d47
тест запрета создания ачивмент юзером
Jan 29, 2024
30643a1
тест запрета создания ачивмент неавторизованным
Jan 29, 2024
46b3a66
test get all achievements superuser
Jan 29, 2024
8da75b4
endpoints, tests
Jan 29, 2024
2a49ebe
endpoints, crud for achievement
Jan 30, 2024
b166e52
тест получения юзером своих ачивментс
Jan 30, 2024
c4c94a7
endpoints, crud, tests for achievement
Jan 30, 2024
b7862d0
добавил тесты запрета удаления ачивмент юзером и неавтризованным
Jan 30, 2024
e9e7f70
добавил тест удаления ачивмент суперюзером
Jan 30, 2024
771eee7
добавил тесты запрета апдейта ачивмент юзером и неавторизованным
Jan 30, 2024
5c24126
endpoints, schemas, tests for achievement
Jan 30, 2024
1321f84
isort
Jan 30, 2024
fc0098d
рефакторинг
Jan 30, 2024
1988797
resolve
Jan 30, 2024
5bb1292
refactoring
Jan 30, 2024
e8f0910
pep8
Jan 30, 2024
496f6f2
Merge branch 'feat/endpoints_group' into feat/test_achievements
Jan 30, 2024
fd61fa5
refactoring
Jan 30, 2024
9774e9c
refactoring status codes
Jan 31, 2024
c9a23a7
добавлена пагинация на гет и тест пагинации
Jan 31, 2024
f0eed70
isort
Jan 31, 2024
1da61dd
Merge branch 'feat/endpoints_group' into feat/test_achievements
Jan 31, 2024
8717665
добавил пагинацию и тесты
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
81 changes: 75 additions & 6 deletions app/api/endpoints/achievement.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,83 @@
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 achievement_crud
from app.schemas.achievement import AchievementCreate, AchievementRead
from app.models import Achievement, User
from app.schemas.achievement import (AchievementCreate, AchievementRead,
AchievementUpdate)
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[AchievementRead],
dependencies=[Depends(current_user)]
dependencies=[Depends(current_superuser)]
)
async def get_all_achievements(
response: Response,
pagination: Pagination = Depends(get_pagination_params),
session: AsyncSession = Depends(get_async_session),
) -> list[AchievementRead]:
"""Возвращает все achievement."""
return await achievement_crud.get_multi(session)
achievements = await achievement_crud.get_multi(session)
response = add_response_headers(
response, achievements, pagination
)
return paginated(achievements, pagination)


@router.get(
'/me',
response_model=list[AchievementRead],
dependencies=[Depends(current_user)]
)
async def get_self_achievements(
user: User = Depends(current_user),
session: AsyncSession = Depends(get_async_session)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Думаю здесь тоже потребуется пагинация

):
"""Возвращает ачивментс юзера."""
return await achievement_crud.get_users_obj(user.id, session)


@router.get(
'/me/{achievement_id}',
response_model=AchievementRead,
dependencies=[Depends(current_user)]
)
async def get_self_achievement_by_id(
achievement_id: int,
user: User = Depends(current_user),
session: AsyncSession = Depends(get_async_session)
):
"""Возвращает ачивмент юзера по id."""
achievement: Achievement = await achievement_crud.get(
achievement_id, session
)
if achievement is None:
raise HTTPException(
status_code=404,
detail='Achievement не существует.'
Copy link
Collaborator

Choose a reason for hiding this comment

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

404 заменить на fastapi status

)
if user.id not in [_.id for _ in achievement.profiles]:
raise HTTPException(
status_code=403,
detail='У выс нет этого achievement.'
Copy link
Collaborator

Choose a reason for hiding this comment

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

fastapi status

)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Небольшая опечатка, заменить на 'вас'

return achievement


@router.post(
"/",
response_model=AchievementRead,
dependencies=[Depends(current_superuser)]
dependencies=[Depends(current_superuser)],
status_code=status.HTTP_201_CREATED
)
async def create_achievement(
achievement: AchievementCreate,
Expand All @@ -37,7 +88,25 @@ async def create_achievement(
return await achievement_crud.create(obj_in=achievement, session=session)


@router.delete("/{obj_id}", dependencies=[Depends(current_superuser)])
@router.patch(
'/{achievement_id}',
response_model=AchievementRead,
dependencies=[Depends(current_superuser)]
)
async def update_achievement(
achievement_id: int,
data: AchievementUpdate,
session: AsyncSession = Depends(get_async_session)
):
"""Апдейт ачивмент."""
_achievement = await achievement_crud.get(achievement_id, session)
return await achievement_crud.update(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Надо при апдейте добавить check_obj_exists для проверки, есть ли объект в бд

_achievement, data, session
)


@router.delete("/{obj_id}", dependencies=[Depends(current_superuser)],
status_code=status.HTTP_204_NO_CONTENT)
async def delete_achievement(
obj_id: int,
session: AsyncSession = Depends(get_async_session),
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],
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
28 changes: 26 additions & 2 deletions app/crud/achievement.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,33 @@
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 Achievement
from app.models import Achievement, Profile


class CRUDAchievement(CRUDBase):
pass
async def get(self, obj_id: int, session: AsyncSession):
stmt = (
select(Achievement)
.where(Achievement.id == obj_id)
.options(
selectinload(Achievement.profiles)
)
)
achievement = await session.execute(stmt)
achievement = achievement.scalars().first()
return achievement

async def get_users_obj(self, user_id: int, session: AsyncSession):
stmt = (
select(Achievement)
.options(
selectinload(Achievement.profiles)
).where(Achievement.profiles.any(Profile.user_id == user_id))
)
db_obj = await session.execute(stmt)
return db_obj.scalars().all()


achievement_crud = CRUDAchievement(Achievement)
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
6 changes: 4 additions & 2 deletions app/models/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class Profile(Base):
ForeignKey('user.id'), unique=True
)
user: Mapped[User] = relationship(back_populates='profile')
achievements: Mapped[Achievement] = relationship(
achievements: Mapped[list[Achievement]] = relationship(
secondary=achievement_profile_association, back_populates="profiles"
)
image: Mapped[str] = Column(
Expand All @@ -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)
Loading
Loading