API - get sports with authenticated user preferences

This commit is contained in:
Sam 2021-11-12 12:33:25 +01:00
parent 8237345edd
commit c05aba92a9
8 changed files with 409 additions and 44 deletions

View File

@ -147,34 +147,52 @@
<span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;sports&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="nt">&quot;color&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Cycling (Sport)&quot;</span>
<span class="nt">&quot;is_active_for_user&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Cycling (Sport)&quot;</span><span class="p">,</span>
<span class="nt">&quot;stopped_speed_threshold&quot;</span><span class="p">:</span> <span class="mi">1</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="nt">&quot;color&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Cycling (Transport)&quot;</span>
<span class="nt">&quot;is_active_for_user&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Cycling (Transport)&quot;</span><span class="p">,</span>
<span class="nt">&quot;stopped_speed_threshold&quot;</span><span class="p">:</span> <span class="mi">1</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="nt">&quot;color&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Hiking&quot;</span>
<span class="nt">&quot;is_active_for_user&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Hiking&quot;</span><span class="p">,</span>
<span class="nt">&quot;stopped_speed_threshold&quot;</span><span class="p">:</span> <span class="mf">0.1</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="nt">&quot;color&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Mountain Biking&quot;</span>
<span class="nt">&quot;is_active_for_user&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Mountain Biking&quot;</span><span class="p">,</span>
<span class="nt">&quot;stopped_speed_threshold&quot;</span><span class="p">:</span> <span class="mi">1</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="nt">&quot;color&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Running&quot;</span>
<span class="nt">&quot;is_active_for_user&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Running&quot;</span><span class="p">,</span>
<span class="nt">&quot;stopped_speed_threshold&quot;</span><span class="p">:</span> <span class="mf">0.1</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="nt">&quot;color&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Walking&quot;</span>
<span class="nt">&quot;is_active_for_user&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Walking&quot;</span><span class="p">,</span>
<span class="nt">&quot;stopped_speed_threshold&quot;</span><span class="p">:</span> <span class="mf">0.1</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="p">},</span>
@ -192,40 +210,58 @@
<span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;sports&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="nt">&quot;color&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;has_workouts&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Cycling (Sport)&quot;</span>
<span class="nt">&quot;is_active_for_user&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Cycling (Sport)&quot;</span><span class="p">,</span>
<span class="nt">&quot;stopped_speed_threshold&quot;</span><span class="p">:</span> <span class="mi">1</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="nt">&quot;color&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;has_workouts&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Cycling (Transport)&quot;</span>
<span class="nt">&quot;is_active_for_user&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Cycling (Transport)&quot;</span><span class="p">,</span>
<span class="nt">&quot;stopped_speed_threshold&quot;</span><span class="p">:</span> <span class="mi">1</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="nt">&quot;color&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;has_workouts&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Hiking&quot;</span>
<span class="nt">&quot;is_active_for_user&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Hiking&quot;</span><span class="p">,</span>
<span class="nt">&quot;stopped_speed_threshold&quot;</span><span class="p">:</span> <span class="mf">0.1</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="nt">&quot;color&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;has_workouts&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Mountain Biking&quot;</span>
<span class="nt">&quot;is_active_for_user&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Mountain Biking&quot;</span><span class="p">,</span>
<span class="nt">&quot;stopped_speed_threshold&quot;</span><span class="p">:</span> <span class="mi">1</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="nt">&quot;color&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;has_workouts&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Running&quot;</span>
<span class="nt">&quot;is_active_for_user&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Running&quot;</span><span class="p">,</span>
<span class="nt">&quot;stopped_speed_threshold&quot;</span><span class="p">:</span> <span class="mf">0.1</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="nt">&quot;color&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;has_workouts&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Walking&quot;</span>
<span class="nt">&quot;is_active_for_user&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Walking&quot;</span><span class="p">,</span>
<span class="nt">&quot;stopped_speed_threshold&quot;</span><span class="p">:</span> <span class="mf">0.1</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="p">},</span>
@ -278,9 +314,12 @@
<span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;sports&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="nt">&quot;color&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Cycling (Sport)&quot;</span>
<span class="nt">&quot;is_active_for_user&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Cycling (Sport)&quot;</span><span class="p">,</span>
<span class="nt">&quot;stopped_speed_threshold&quot;</span><span class="p">:</span> <span class="mi">1</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="p">},</span>
@ -298,10 +337,13 @@
<span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;sports&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="nt">&quot;color&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;has_workouts&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Cycling (Sport)&quot;</span>
<span class="nt">&quot;is_active_for_user&quot;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Cycling (Sport)&quot;</span><span class="p">,</span>
<span class="nt">&quot;stopped_speed_threshold&quot;</span><span class="p">:</span> <span class="mi">1</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="p">},</span>
@ -371,10 +413,13 @@ Authenticated user must be an admin</p>
<span class="nt">&quot;data&quot;</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">&quot;sports&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="nt">&quot;color&quot;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="nt">&quot;has_workouts&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;id&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;is_active&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Cycling (Sport)&quot;</span>
<span class="nt">&quot;is_active_for_user&quot;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="nt">&quot;label&quot;</span><span class="p">:</span> <span class="s2">&quot;Cycling (Sport)&quot;</span><span class="p">,</span>
<span class="nt">&quot;stopped_speed_threshold&quot;</span><span class="p">:</span> <span class="mi">1</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="p">},</span>

File diff suppressed because one or more lines are too long

View File

@ -96,3 +96,17 @@ def user_sport_1_preference(
db.session.add(user_sport)
db.session.commit()
return user_sport
@pytest.fixture()
def user_admin_sport_1_preference(
user_1_admin: User, sport_1_cycling: Sport
) -> UserSportPreference:
user_sport = UserSportPreference(
user_id=user_1_admin.id,
sport_id=sport_1_cycling.id,
stopped_speed_threshold=sport_1_cycling.stopped_speed_threshold,
)
db.session.add(user_sport)
db.session.commit()
return user_sport

View File

@ -2,7 +2,8 @@ import json
from flask import Flask
from fittrackee.users.models import User
from fittrackee import db
from fittrackee.users.models import User, UserSportPreference
from fittrackee.workouts.models import Sport, Workout
from ..api_test_case import ApiTestCaseMixin
@ -11,6 +12,9 @@ expected_sport_1_cycling_result = {
'id': 1,
'label': 'Cycling',
'is_active': True,
'is_active_for_user': True,
'color': None,
'stopped_speed_threshold': 1,
}
expected_sport_1_cycling_admin_result = expected_sport_1_cycling_result.copy()
expected_sport_1_cycling_admin_result['has_workouts'] = False
@ -19,6 +23,9 @@ expected_sport_2_running_result = {
'id': 2,
'label': 'Running',
'is_active': True,
'is_active_for_user': True,
'color': None,
'stopped_speed_threshold': 0.1,
}
expected_sport_2_running_admin_result = expected_sport_2_running_result.copy()
expected_sport_2_running_admin_result['has_workouts'] = False
@ -27,6 +34,9 @@ expected_sport_1_cycling_inactive_result = {
'id': 1,
'label': 'Cycling',
'is_active': False,
'is_active_for_user': False,
'color': None,
'stopped_speed_threshold': 1,
}
expected_sport_1_cycling_inactive_admin_result = (
expected_sport_1_cycling_inactive_result.copy()
@ -108,6 +118,39 @@ class TestGetSports(ApiTestCaseMixin):
data['data']['sports'][1] == expected_sport_2_running_admin_result
)
def test_it_gets_sports_with_auth_user_preferences(
self,
app: Flask,
user_1_admin: User,
sport_1_cycling: Sport,
sport_2_running: Sport,
user_admin_sport_1_preference: UserSportPreference,
) -> None:
user_admin_sport_1_preference.color = '#000000'
user_admin_sport_1_preference.stopped_speed_threshold = 0.5
user_admin_sport_1_preference.is_active = False
db.session.commit()
client, auth_token = self.get_test_client_and_auth_token(
app, as_admin=True
)
response = client.get(
'/api/sports',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['sports']) == 2
assert data['data']['sports'][0]['color'] == '#000000'
assert data['data']['sports'][0]['stopped_speed_threshold'] == 0.5
assert data['data']['sports'][0]['is_active_for_user'] is False
assert (
data['data']['sports'][1] == expected_sport_2_running_admin_result
)
class TestGetSport(ApiTestCaseMixin):
def test_it_gets_a_sport(
@ -126,6 +169,26 @@ class TestGetSport(ApiTestCaseMixin):
assert len(data['data']['sports']) == 1
assert data['data']['sports'][0] == expected_sport_1_cycling_result
def test_it_gets_a_sport_with_preferences(
self,
app: Flask,
user_1: User,
sport_1_cycling: Sport,
user_sport_1_preference: UserSportPreference,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(app)
response = client.get(
'/api/sports/1',
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['sports']) == 1
assert data['data']['sports'][0] == expected_sport_1_cycling_result
def test_it_returns_404_if_sport_does_not_exist(
self, app: Flask, user_1: User
) -> None:
@ -202,6 +265,7 @@ class TestUpdateSport(ApiTestCaseMixin):
assert 'success' in data['status']
assert len(data['data']['sports']) == 1
assert data['data']['sports'][0]['is_active'] is False
assert data['data']['sports'][0]['is_active_for_user'] is False
assert data['data']['sports'][0]['has_workouts'] is False
def test_it_enables_a_sport(
@ -224,6 +288,7 @@ class TestUpdateSport(ApiTestCaseMixin):
assert 'success' in data['status']
assert len(data['data']['sports']) == 1
assert data['data']['sports'][0]['is_active'] is True
assert data['data']['sports'][0]['is_active_for_user'] is True
assert data['data']['sports'][0]['has_workouts'] is False
def test_it_disables_a_sport_with_workouts(
@ -249,6 +314,7 @@ class TestUpdateSport(ApiTestCaseMixin):
assert 'success' in data['status']
assert len(data['data']['sports']) == 1
assert data['data']['sports'][0]['is_active'] is False
assert data['data']['sports'][0]['is_active_for_user'] is False
assert data['data']['sports'][0]['has_workouts'] is True
def test_it_enables_a_sport_with_workouts(
@ -275,8 +341,63 @@ class TestUpdateSport(ApiTestCaseMixin):
assert 'success' in data['status']
assert len(data['data']['sports']) == 1
assert data['data']['sports'][0]['is_active'] is True
assert data['data']['sports'][0]['is_active_for_user'] is True
assert data['data']['sports'][0]['has_workouts'] is True
def test_it_disables_a_sport_with_preferences(
self,
app: Flask,
user_1_admin: User,
sport_1_cycling: Sport,
user_admin_sport_1_preference: UserSportPreference,
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, as_admin=True
)
response = client.patch(
'/api/sports/1',
content_type='application/json',
data=json.dumps(dict(is_active=False)),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['sports']) == 1
assert data['data']['sports'][0]['is_active'] is False
assert data['data']['sports'][0]['is_active_for_user'] is False
assert data['data']['sports'][0]['is_active_for_user'] is False
assert data['data']['sports'][0]['has_workouts'] is False
def test_it_enables_a_sport_with_preferences(
self,
app: Flask,
user_1_admin: User,
sport_1_cycling: Sport,
user_admin_sport_1_preference: UserSportPreference,
) -> None:
sport_1_cycling.is_active = False
client, auth_token = self.get_test_client_and_auth_token(
app, as_admin=True
)
response = client.patch(
'/api/sports/1',
content_type='application/json',
data=json.dumps(dict(is_active=True)),
headers=dict(Authorization=f'Bearer {auth_token}'),
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['sports']) == 1
assert data['data']['sports'][0]['is_active'] is True
assert data['data']['sports'][0]['is_active_for_user'] is True
assert data['data']['sports'][0]['has_workouts'] is False
def test_returns_error_if_user_has_no_admin_rights(
self, app: Flask, user_1: User, sport_1_cycling: Sport
) -> None:

View File

@ -2,7 +2,8 @@ from typing import Dict, Optional
from flask import Flask
from fittrackee.users.models import User
from fittrackee import db
from fittrackee.users.models import User, UserSportPreference
from fittrackee.workouts.models import Sport, Workout
@ -15,10 +16,13 @@ class TestSportModel:
assert 'Cycling' == sport.label
assert '<Sport \'Cycling\'>' == str(sport)
serialized_sport = sport.serialize(is_admin)
serialized_sport = sport.serialize(is_admin=is_admin)
assert 1 == serialized_sport['id']
assert 'Cycling' == serialized_sport['label']
assert serialized_sport['is_active'] is True
assert serialized_sport['is_active_for_user'] is True
assert serialized_sport['color'] is None
assert serialized_sport['stopped_speed_threshold'] == 1
return serialized_sport
def test_sport_model(self, app: Flask, sport_1_cycling: Sport) -> None:
@ -44,3 +48,87 @@ class TestSportModel:
) -> None:
serialized_sport = self.assert_sport_model(sport_1_cycling, True)
assert serialized_sport['has_workouts'] is True
class TestSportModelWithPreferences:
def test_sport_model_with_color_preference(
self,
app: Flask,
user_1: User,
sport_1_cycling: Sport,
user_sport_1_preference: UserSportPreference,
) -> None:
user_sport_1_preference.color = '#00000'
serialized_sport = sport_1_cycling.serialize(
sport_preferences=user_sport_1_preference.serialize()
)
assert serialized_sport['id'] == 1
assert serialized_sport['label'] == 'Cycling'
assert serialized_sport['is_active'] is True
assert serialized_sport['is_active_for_user'] is True
assert serialized_sport['color'] == '#00000'
assert serialized_sport['stopped_speed_threshold'] == 1
assert 'has_workouts' not in serialized_sport
def test_sport_model_with_is_active_preference(
self,
app: Flask,
user_1: User,
sport_1_cycling: Sport,
user_sport_1_preference: UserSportPreference,
) -> None:
user_sport_1_preference.is_active = False
serialized_sport = sport_1_cycling.serialize(
sport_preferences=user_sport_1_preference.serialize()
)
assert serialized_sport['id'] == 1
assert serialized_sport['label'] == 'Cycling'
assert serialized_sport['is_active'] is True
assert serialized_sport['is_active_for_user'] is False
assert serialized_sport['color'] is None
assert serialized_sport['stopped_speed_threshold'] == 1
assert 'has_workouts' not in serialized_sport
def test_inactive_sport_model_with_is_active_preference(
self,
app: Flask,
user_1: User,
sport_1_cycling: Sport,
user_sport_1_preference: UserSportPreference,
) -> None:
sport_1_cycling.is_active = False
user_sport_1_preference.is_active = True
serialized_sport = sport_1_cycling.serialize(
sport_preferences=user_sport_1_preference.serialize()
)
assert serialized_sport['id'] == 1
assert serialized_sport['label'] == 'Cycling'
assert serialized_sport['is_active'] is False
assert serialized_sport['is_active_for_user'] is False
assert serialized_sport['color'] is None
assert serialized_sport['stopped_speed_threshold'] == 1
assert 'has_workouts' not in serialized_sport
def test_sport_model_with_threshold_preference(
self,
app: Flask,
user_1: User,
sport_1_cycling: Sport,
user_sport_1_preference: UserSportPreference,
) -> None:
user_sport_1_preference.stopped_speed_threshold = 0.5
db.session.commit()
serialized_sport = sport_1_cycling.serialize(
sport_preferences=user_sport_1_preference.serialize()
)
assert serialized_sport['id'] == 1
assert serialized_sport['label'] == 'Cycling'
assert serialized_sport['is_active'] is True
assert serialized_sport['is_active_for_user'] is True
assert serialized_sport['color'] is None
assert serialized_sport['stopped_speed_threshold'] == 0.5
assert 'has_workouts' not in serialized_sport

View File

@ -40,11 +40,6 @@ class User(BaseModel):
'Record', lazy=True, backref=db.backref('user', lazy='joined')
)
language = db.Column(db.String(50), nullable=True)
sport_preferences = db.relationship(
'UserSportPreference',
lazy=True,
backref=db.backref('user', lazy='joined'),
)
def __repr__(self) -> str:
return f'<User {self.username!r}>'

View File

@ -85,11 +85,30 @@ class Sport(BaseModel):
def __init__(self, label: str) -> None:
self.label = label
def serialize(self, is_admin: Optional[bool] = False) -> Dict:
def serialize(
self,
is_admin: Optional[bool] = False,
sport_preferences: Optional[Dict] = None,
) -> Dict:
serialized_sport = {
'id': self.id,
'label': self.label,
'is_active': self.is_active,
'is_active_for_user': (
self.is_active
if sport_preferences is None
else (sport_preferences['is_active'] and self.is_active)
),
'color': (
None
if sport_preferences is None
else sport_preferences['color']
),
'stopped_speed_threshold': (
self.stopped_speed_threshold
if sport_preferences is None
else sport_preferences['stopped_speed_threshold']
),
}
if is_admin:
serialized_sport['has_workouts'] = len(self.workouts) > 0

View File

@ -11,7 +11,7 @@ from fittrackee.responses import (
handle_error_and_return_response,
)
from fittrackee.users.decorators import authenticate, authenticate_as_admin
from fittrackee.users.models import User
from fittrackee.users.models import User, UserSportPreference
from .models import Sport
@ -44,34 +44,52 @@ def get_sports(auth_user_id: int) -> Dict:
"data": {
"sports": [
{
"color": null,
"id": 1,
"is_active": true,
"label": "Cycling (Sport)"
"is_active_for_user": true,
"label": "Cycling (Sport)",
"stopped_speed_threshold": 1
},
{
"color": null,
"id": 2,
"is_active": true,
"label": "Cycling (Transport)"
"is_active_for_user": true,
"label": "Cycling (Transport)",
"stopped_speed_threshold": 1
},
{
"color": null,
"id": 3,
"is_active": true,
"label": "Hiking"
"is_active_for_user": true,
"label": "Hiking",
"stopped_speed_threshold": 0.1
},
{
"color": null,
"id": 4,
"is_active": true,
"label": "Mountain Biking"
"is_active_for_user": true,
"label": "Mountain Biking",
"stopped_speed_threshold": 1
},
{
"color": null,
"id": 5,
"is_active": true,
"label": "Running"
"is_active_for_user": true,
"label": "Running",
"stopped_speed_threshold": 0.1
},
{
"color": null,
"id": 6,
"is_active": true,
"label": "Walking"
"is_active_for_user": true,
"label": "Walking",
"stopped_speed_threshold": 0.1
}
]
},
@ -89,40 +107,58 @@ def get_sports(auth_user_id: int) -> Dict:
"data": {
"sports": [
{
"color": null,
"has_workouts": true,
"id": 1,
"is_active": true,
"label": "Cycling (Sport)"
"is_active_for_user": true,
"label": "Cycling (Sport)",
"stopped_speed_threshold": 1
},
{
"color": null,
"has_workouts": false,
"id": 2,
"is_active": true,
"label": "Cycling (Transport)"
"is_active_for_user": true,
"label": "Cycling (Transport)",
"stopped_speed_threshold": 1
},
{
"color": null,
"has_workouts": false,
"id": 3,
"is_active": true,
"label": "Hiking"
"is_active_for_user": true,
"label": "Hiking",
"stopped_speed_threshold": 0.1
},
{
"color": null,
"has_workouts": false,
"id": 4,
"is_active": true,
"label": "Mountain Biking"
"is_active_for_user": true,
"label": "Mountain Biking",
"stopped_speed_threshold": 1
},
{
"color": null,
"has_workouts": false,
"id": 5,
"is_active": true,
"label": "Running"
"is_active_for_user": true,
"label": "Running",
"stopped_speed_threshold": 0.1
},
{
"color": null,
"has_workouts": false,
"id": 6,
"is_active": true,
"label": "Walking"
"is_active_for_user": true,
"label": "Walking",
"stopped_speed_threshold": 0.1
}
]
},
@ -142,9 +178,22 @@ def get_sports(auth_user_id: int) -> Dict:
"""
user = User.query.filter_by(id=int(auth_user_id)).first()
sports = Sport.query.order_by(Sport.id).all()
sports_data = []
for sport in sports:
sport_preferences = UserSportPreference.query.filter_by(
user_id=user.id, sport_id=sport.id
).first()
sports_data.append(
sport.serialize(
is_admin=user.admin,
sport_preferences=sport_preferences.serialize()
if sport_preferences
else None,
)
)
return {
'status': 'success',
'data': {'sports': [sport.serialize(user.admin) for sport in sports]},
'data': {'sports': sports_data},
}
@ -174,9 +223,12 @@ def get_sport(auth_user_id: int, sport_id: int) -> Union[Dict, HttpResponse]:
"data": {
"sports": [
{
"color": null,
"id": 1,
"is_active": true,
"label": "Cycling (Sport)"
"is_active_for_user": true,
"label": "Cycling (Sport)",
"stopped_speed_threshold": 1
}
]
},
@ -194,10 +246,13 @@ def get_sport(auth_user_id: int, sport_id: int) -> Union[Dict, HttpResponse]:
"data": {
"sports": [
{
"color": null,
"has_workouts": false,
"id": 1,
"is_active": true,
"label": "Cycling (Sport)"
"is_active_for_user": true,
"label": "Cycling (Sport)",
"stopped_speed_threshold": 1
}
]
},
@ -234,9 +289,21 @@ def get_sport(auth_user_id: int, sport_id: int) -> Union[Dict, HttpResponse]:
user = User.query.filter_by(id=int(auth_user_id)).first()
sport = Sport.query.filter_by(id=sport_id).first()
if sport:
sport_preferences = UserSportPreference.query.filter_by(
user_id=user.id, sport_id=sport.id
).first()
return {
'status': 'success',
'data': {'sports': [sport.serialize(user.admin)]},
'data': {
'sports': [
sport.serialize(
is_admin=user.admin,
sport_preferences=sport_preferences.serialize()
if sport_preferences
else None,
)
]
},
}
return DataNotFoundErrorResponse('sports')
@ -270,10 +337,13 @@ def update_sport(
"data": {
"sports": [
{
"color": null,
"has_workouts": false,
"id": 1,
"is_active": false,
"label": "Cycling (Sport)"
"is_active_for_user": false,
"label": "Cycling (Sport)",
"stopped_speed_threshold": 1
}
]
},
@ -317,15 +387,28 @@ def update_sport(
return InvalidPayloadErrorResponse()
try:
user = User.query.filter_by(id=int(auth_user_id)).first()
sport = Sport.query.filter_by(id=sport_id).first()
if not sport:
return DataNotFoundErrorResponse('sports')
sport.is_active = sport_data.get('is_active')
db.session.commit()
sport_preferences = UserSportPreference.query.filter_by(
user_id=user.id, sport_id=sport.id
).first()
return {
'status': 'success',
'data': {'sports': [sport.serialize(True)]},
'data': {
'sports': [
sport.serialize(
is_admin=user.admin,
sport_preferences=sport_preferences.serialize()
if sport_preferences
else None,
)
]
},
}
except (exc.IntegrityError, exc.OperationalError, ValueError) as e: