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

Feature/exercise #227

Open
wants to merge 86 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
a557ed9
add exercise to calendar
orontamir Feb 4, 2021
b069b84
Merge branch 'develop' into oron_exercise
orontamir Feb 4, 2021
ae0f2f2
fix warning
orontamir Feb 4, 2021
8907a4f
fix missing new line
orontamir Feb 4, 2021
4e14436
fix config file
orontamir Feb 4, 2021
5866aa6
Update unit test
orontamir Feb 4, 2021
731c0a9
fix blank line
orontamir Feb 4, 2021
67deec3
fix trailing whitespace
orontamir Feb 4, 2021
16e2e0f
fix PureDreamer issues
orontamir Feb 5, 2021
4186ead
fix unused import
orontamir Feb 5, 2021
96f514d
fix ivarshav's issues
orontamir Feb 5, 2021
cb9bc6f
fix unit test
orontamir Feb 5, 2021
fe6a45f
Merge branch 'develop' into oron_exercise
orontamir Feb 7, 2021
62e5134
fix Yam Comment
orontamir Feb 7, 2021
6ff8680
Merge branch 'oron_exercise' of https://github.com/orontamir/calendar…
orontamir Feb 7, 2021
83bf989
switch session user
orontamir Feb 7, 2021
d069a0b
add space between parameters
orontamir Feb 7, 2021
419138f
add session=session and user=user
orontamir Feb 7, 2021
af3d449
Add exercise tag
orontamir Feb 7, 2021
9269c23
Merge branch 'develop' into oron_exercise
orontamir Feb 7, 2021
14af5ae
remove unused import
orontamir Feb 7, 2021
bc39b5d
update test
orontamir Feb 7, 2021
6b8b70e
remove test_process_new_event because its show twise
orontamir Feb 7, 2021
1eb57a3
fix TestBotClient test
orontamir Feb 7, 2021
33dc416
fix test_user_registered
orontamir Feb 7, 2021
4aea280
remove new line
orontamir Feb 7, 2021
7801291
fix TestBotClient tests
orontamir Feb 7, 2021
4cb0dbf
Fix Yam Comments
orontamir Feb 9, 2021
5dfbad5
fix build issues
orontamir Feb 9, 2021
135314e
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
orontamir Feb 9, 2021
d4a5816
update date time
orontamir Feb 9, 2021
b85bc90
Fix event tests and user_exercise test
orontamir Feb 9, 2021
be08a20
fix line too long
orontamir Feb 9, 2021
9b275fc
fix merging
orontamir Feb 9, 2021
4d30d68
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
orontamir Feb 10, 2021
ec9b90e
fix unused import
orontamir Feb 10, 2021
60a0983
fix remove unused import
orontamir Feb 10, 2021
82ace54
fix test add save to user exercise
orontamir Feb 10, 2021
cf5f221
fix import in exercise
orontamir Feb 10, 2021
8d02b1b
fix put two blank line
orontamir Feb 10, 2021
6721204
fix tests in test user exercise
orontamir Feb 10, 2021
03d93b6
fix remove space
orontamir Feb 10, 2021
586212e
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
orontamir Feb 10, 2021
9dd9447
fix Yam Comments
orontamir Feb 10, 2021
72f7fc3
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
orontamir Feb 10, 2021
239706f
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
orontamir Feb 11, 2021
866ba66
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
orontamir Feb 11, 2021
6036409
Fix Yam issues
orontamir Feb 11, 2021
ba7d670
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
orontamir Feb 11, 2021
dfd93e2
fix the issue blank 2 lines
orontamir Feb 11, 2021
5e719d1
fix tests user exercise
orontamir Feb 11, 2021
ee7737e
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
orontamir Feb 13, 2021
8e2e13e
add html file
orontamir Feb 14, 2021
c5bc680
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
orontamir Feb 14, 2021
79c4c71
update html files
orontamir Feb 14, 2021
b3b4516
update html files
orontamir Feb 14, 2021
06c4c8a
update html files
orontamir Feb 14, 2021
9239a3e
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
orontamir Feb 15, 2021
26ae6f2
Remove all exercise images
orontamir Feb 15, 2021
98318f6
fix line too long
orontamir Feb 15, 2021
4ed9fc4
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
orontamir Feb 15, 2021
58f2bb9
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
orontamir Feb 15, 2021
ed39214
Merge branch 'develop' into oron_exercise
orontamir Feb 16, 2021
3e2549c
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
orontamir Feb 16, 2021
7ff5475
Merge branch 'oron_exercise' of https://github.com/orontamir/calendar…
orontamir Feb 16, 2021
401e663
fix exception
orontamir Feb 16, 2021
13e07a0
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
orontamir Feb 17, 2021
41be098
Remove htm file and update to PNG file
orontamir Feb 17, 2021
eead190
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
orontamir Feb 18, 2021
afff575
fix 2 blank lines
orontamir Feb 18, 2021
235acb5
Add pytest.fixture to user exercise
orontamir Feb 18, 2021
ad0e25b
Write by myself exercises
orontamir Feb 19, 2021
952f5f4
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
orontamir Feb 20, 2021
3fff6d8
add exercise to feature
orontamir Feb 20, 2021
5083bcb
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
orontamir Feb 21, 2021
6447cc0
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
orontamir Feb 21, 2021
1ecc062
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
orontamir Feb 25, 2021
e424f6e
add Session import
orontamir Feb 25, 2021
b04e125
import datetime
orontamir Feb 25, 2021
8287a7b
fix test user
orontamir Feb 25, 2021
fa5b4ee
fix two blank line
orontamir Feb 25, 2021
a923103
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
orontamir Mar 7, 2021
7ad9aa0
add new line
orontamir Mar 7, 2021
ec126c3
remove new line
orontamir Mar 7, 2021
5712c63
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
orontamir Apr 22, 2021
6f784b5
update test exercise
orontamir Apr 22, 2021
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
3 changes: 3 additions & 0 deletions app/config.py.example
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,6 @@ LOG_FORMAT = ("<level>{level: <8}</level>"
" <green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green>"
" - <cyan>{name}</cyan>:<cyan>{function}</cyan>"
" - <level>{message}</level>")

# EXERCISE_FILE_PATH
EXERCISE_FILE = "images/exercises/Exercise_{num}.png"
13 changes: 13 additions & 0 deletions app/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class User(Base):
avatar = Column(String, default="profile.png")
telegram_id = Column(String, unique=True)
is_active = Column(Boolean, default=False)
is_active_exercise = Column(Boolean, default=False)
language_id = Column(Integer, ForeignKey("languages.id"))

owned_events = relationship(
Expand All @@ -48,6 +49,18 @@ def __repr__(self):
return f'<User {self.id}>'


class UserExercise(Base):
"""
Table name user exercise
Save when user start his exercise
"""
__tablename__ = "user_exercise"

id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"))
start_date = Column(DateTime, nullable=False)


class Event(Base):
__tablename__ = "events"

Expand Down
3 changes: 2 additions & 1 deletion app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def create_tables(engine, psql_environment):

from app.routers import ( # noqa: E402
agenda, calendar, categories, celebrity, currency, dayview,
email, event, invitation, profile, search, telegram, whatsapp
email, event, exercise, invitation, profile, search, telegram, whatsapp
)

json_data_loader.load_to_db(next(get_db()))
Expand All @@ -47,6 +47,7 @@ def create_tables(engine, psql_environment):
dayview.router,
email.router,
event.router,
exercise.router,
invitation.router,
profile.router,
salary.router,
Expand Down
3 changes: 2 additions & 1 deletion app/routers/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ def is_fields_types_valid(to_check: Dict[str, Any], types: Dict[str, Any]):
"""validate dictionary values by dictionary of types"""
errors = []
for field_name, field_type in to_check.items():
if types[field_name] and not isinstance(field_type, types[field_name]):
if types[field_name] and not isinstance(field_type,
(types[field_name])):
errors.append(
f"{field_name} is '{type(field_type).__name__}' and"
+ f"it should be from type '{types[field_name].__name__}'")
Expand Down
47 changes: 47 additions & 0 deletions app/routers/exercise.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from fastapi import APIRouter, Depends, Request
from app.database.models import User
from app.dependencies import get_db, templates
from app.routers.user_exercise import get_user_exercise
from datetime import datetime
from app import config
from sqlalchemy.orm import Session

router = APIRouter(
prefix="/exercise",
tags=["exercise"],
responses={404: {"description": "Not found"}},
)


@router.get("/")
async def exercise(
request: Request,
session: Session = Depends(get_db),
):
"""
If is active exercise = True
Show user exercise for a specific day if is_active_exercise is ture.
"""
user = session.query(User).filter_by(id=1).first()
if not user:
# create empty default user
user = User(
username='',
password='',
email=''
)
# Get user exercise
user_exercise = get_user_exercise(session, user_id=user.id)
if user_exercise:
# Get exercise day
delta = datetime.now() - user_exercise[0].start_date
# All exercises split to 30 days
day = (delta.days % 30) + 1
orontamir marked this conversation as resolved.
Show resolved Hide resolved
else:
day = 1
exercise_day = str(config.EXERCISE_FILE.format(num=day))
return templates.TemplateResponse("exercise.html", {
"request": request,
"user": user,
"exercise": exercise_day
})
29 changes: 29 additions & 0 deletions app/routers/profile.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import io

from app.routers.user_exercise import create_user_exercise
from fastapi import APIRouter, Depends, File, Request, UploadFile
from PIL import Image
from sqlalchemy.orm import Session
from starlette.responses import RedirectResponse
from starlette.status import HTTP_302_FOUND

Expand All @@ -28,6 +30,7 @@ def get_placeholder_user():
full_name='My Name',
language_id=1,
telegram_id='',
is_active_exercise=False,
language='english',
)

Expand Down Expand Up @@ -59,6 +62,32 @@ async def profile(
})


@router.get("/exercise/start")
async def start_exercise(session: Session = Depends(get_db)):
user = session.query(User).filter_by(id=1).first()

# Update database
user.is_active_exercise = True
session.commit()

# create user exercise
create_user_exercise(session, user)
url = router.url_path_for("profile")
return RedirectResponse(url=url, status_code=HTTP_302_FOUND)


@router.get("/exercise/stop")
async def stop_exercise(session=Depends(get_db)):
user = session.query(User).filter_by(id=1).first()

# Update database
user.is_active_exercise = False
session.commit()

url = router.url_path_for("profile")
return RedirectResponse(url=url, status_code=HTTP_302_FOUND)


@router.post("/update_user_fullname")
async def update_user_fullname(
request: Request, session=Depends(get_db)):
Expand Down
53 changes: 53 additions & 0 deletions app/routers/user_exercise.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from datetime import datetime
from app.database.models import Base, User, UserExercise


def save(session: Session, instance: Base) -> bool:
"""Commits an instance to the db.
source: app.database.models.Base"""

if issubclass(instance.__class__, Base):
session.add(instance)
session.commit()
return True
return False


def create_user_exercise(session: Session, user: User) -> UserExercise:
""" Create and save new user exercise """
orontamir marked this conversation as resolved.
Show resolved Hide resolved
if not does_user_exercise_exist(session=session, user_id=user.id):
user_exercise = UserExercise(
user_id=user.id,
start_date=datetime.now()
)
save(session=session, instance=user_exercise)
else:
user_exercise = update_user_exercise(session=session, user=user)
return user_exercise


def does_user_exercise_exist(session: Session, user_id: int) -> bool:
"""
orontamir marked this conversation as resolved.
Show resolved Hide resolved
Check if a user exercise for user id exists.
"""
return get_user_exercise(session=session, user_id=user_id)


def get_user_exercise(session: Session, **param) -> list:
"""Returns user exercise filter by param."""
try:
user_exercise = list(session.query(UserExercise).filter_by(**param))
except SQLAlchemyError:
return []
else:
return user_exercise


def update_user_exercise(session: Session, user: User) -> UserExercise:
user_ex = session.query(UserExercise).filter_by(user_id=user.id).first()
# Update database
user_ex.start_date = datetime.now()
session.commit()
return user_ex
orontamir marked this conversation as resolved.
Show resolved Hide resolved
Binary file added app/static/images/exercises/Exercise_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_10.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_11.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_12.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_13.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_14.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_15.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_17.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_18.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_19.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_20.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_21.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_22.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_23.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_24.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_25.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_26.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_27.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_28.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_29.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_30.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/static/images/exercises/Exercise_5.png
Binary file added app/static/images/exercises/Exercise_6.png
Binary file added app/static/images/exercises/Exercise_7.png
Binary file added app/static/images/exercises/Exercise_8.png
Binary file added app/static/images/exercises/Exercise_9.png
10 changes: 8 additions & 2 deletions app/static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ body {
}

.profile-modal-header {
border: none;
background-color: whitesmoke;
border: none;
background-color:whitesmoke;
orontamir marked this conversation as resolved.
Show resolved Hide resolved
}

.exercise {
font-family: 'Assistant', sans-serif;
text-align: center;
}

3 changes: 3 additions & 0 deletions app/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@
<li class="nav-item">
<a class="nav-link" href="/search">Search</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('exercise') }}">Exercise</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/celebrity">Celebrities Born Today</a>
</li>
Expand Down
20 changes: 20 additions & 0 deletions app/templates/exercise.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{% extends "base.html" %}


{% block content %}

<div class="container mt-4">
<div class="row">
<!-- Right side -->
<div class="col-5">
<!-- Exercise -->
{% if user.is_active_exercise %}
<div class="exercise">
<img src="{{ url_for('static', path=exercise) }}" alt="exercise image" width="270%" height="200%">
</div>
{% endif %}
</div>
</div>
</div>

{% endblock %}
3 changes: 3 additions & 0 deletions app/templates/partials/index/navigation.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
<li class="nav-item">
<a class="nav-link" href="/search">Search</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('exercise') }}">Exercise</a>
</li>
</ul>
</div>
</div>
Expand Down
9 changes: 9 additions & 0 deletions app/templates/profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,15 @@ <h6 class="card-title text-center mb-1">{{ user.full_name }}</h6>
<li class="list-group-item list-group-item-action no-border">
<a class="text-decoration-none text-secondary" href="#">Export my calendar</a>
</li>
{% if not user.is_active_exercise %}
<li class="list-group-item list-group-item-action no-border">
<a class="text-decoration-none text-secondary" href="./exercise/start">Start Exercise</a>
orontamir marked this conversation as resolved.
Show resolved Hide resolved
</li>
{% else %}
<li class="list-group-item list-group-item-action no-border">
<a class="text-decoration-none text-secondary" href="./exercise/stop">Stop Exercise</a>
</li>
{% endif %}
<li class="list-group-item list-group-item-action no-border">
<a class="text-decoration-none text-secondary" href="#">Your feature</a>
</li>
Expand Down
38 changes: 38 additions & 0 deletions tests/test_user_exercise.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from app.routers.user_exercise import create_user_exercise,\
does_user_exercise_exist, get_user_exercise
from app.routers.user import create_user


class TestUserExercise:

def test_create_user_exercise(self, session):
orontamir marked this conversation as resolved.
Show resolved Hide resolved
_user = create_user(
session=session,
username='new_test_username',
password='new_test_password',
email='[email protected]',
language='english',
language_id=1,
)
user_exercise = create_user_exercise(
session=session,
user=_user
)
assert user_exercise.user_id == _user.id
session.delete(user_exercise)
session.delete(_user)
session.commit()

def test_get_users_exercise_success(self, user_exercise, session):
assert get_user_exercise(user_id=user_exercise.user_id,
session=session) == [user_exercise]

def test_get_user_exercise_failure(self, user_exercise, session):
assert get_user_exercise(user_id=100, session=session) == []

def test_does_user_exercise_exist_success(self, user_exercise, session):
assert does_user_exercise_exist(session=session,
user_id=user_exercise.user_id)

def test_does_user_exercise_exist_failure(self, session):
assert not does_user_exercise_exist(session=session, user_id=100)
15 changes: 13 additions & 2 deletions tests/user_fixture.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest
from sqlalchemy.orm import Session

from app.database.models import User
from datetime import datetime
orontamir marked this conversation as resolved.
Show resolved Hide resolved
from app.database.models import User, UserExercise
from app.internal.utils import create_model, delete_instance


Expand All @@ -19,6 +19,17 @@ def user(session: Session) -> User:
delete_instance(session, test_user)


@pytest.fixture
def user_exercise(session: Session, user: User) -> UserExercise:
test_user_exercise = create_model(
session, UserExercise,
user_id=11,
start_date=datetime.now()
)
yield test_user_exercise
delete_instance(session, test_user_exercise)


@pytest.fixture
def sender(session: Session) -> User:
sender = create_model(
Expand Down