Skip to content

Commit a965e9b

Browse files
committed
Add Aggregrate API Backend
1 parent 1d9be2b commit a965e9b

15 files changed

+292
-22
lines changed

open/core/betterself/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class BetterSelfResourceConstants:
2424
WELL_BEING_LOGS = "well_being_logs"
2525
OVERVIEW = "overview"
2626
DAILY_REVIEW = "daily_review"
27+
AGGREGATE = "aggregate"
2728

2829

2930
WEB_INPUT_SOURCE = "web"
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from rest_framework.fields import DateField, ListField, UUIDField
2+
from rest_framework.serializers import Serializer
3+
4+
from open.core.betterself.models.activity import Activity
5+
from open.core.betterself.models.food import Food
6+
from open.core.betterself.models.supplement import Supplement
7+
from open.core.betterself.serializers.validators import (
8+
generic_model_uuid_validator,
9+
)
10+
11+
12+
class AggregrateViewParamsSerializer(Serializer):
13+
start_date = DateField()
14+
end_date = DateField()
15+
supplement_uuids = ListField(
16+
child=UUIDField(validators=[generic_model_uuid_validator(Supplement)]),
17+
required=False,
18+
)
19+
activity_uuids = ListField(
20+
child=UUIDField(validators=[generic_model_uuid_validator(Activity)]),
21+
required=False,
22+
)
23+
food_uuids = ListField(
24+
child=UUIDField(validators=[generic_model_uuid_validator(Food)]), required=False
25+
)
26+
27+
class Meta:
28+
fields = (
29+
"start_date",
30+
"end_date",
31+
"supplement_uuids",
32+
"activity_uuids",
33+
"food_uuids",
34+
)

open/core/betterself/serializers/ingredient_composition_serializers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,11 @@ class Meta:
5757

5858
def validate_ingredient_uuid(self, value):
5959
user = self.context["request"].user
60-
validate_model_uuid(Ingredient, uuid=value, user=user)
60+
validate_model_uuid(uuid=value, model=Ingredient, user=user)
6161
return value
6262

6363
def validate_measurement_uuid(self, value):
64-
validate_model_uuid(Measurement, uuid=value)
64+
validate_model_uuid(uuid=value, model=Measurement)
6565
return value
6666

6767
def validate(self, validated_data):
@@ -89,7 +89,7 @@ def validate(self, validated_data):
8989
quantity=validated_data["quantity"],
9090
).exists():
9191
raise ValidationError(
92-
f"Fields user, ingredient, measurement, and quantity are not unique!"
92+
"Fields user, ingredient, measurement, and quantity are not unique!"
9393
)
9494

9595
return validated_data

open/core/betterself/serializers/supplement_log_serializers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,14 @@ class Meta:
8484
def validate_supplement_uuid(self, value):
8585
user = self.context["request"].user
8686
try:
87-
validate_model_uuid(Supplement, uuid=value, user=user)
87+
validate_model_uuid(uuid=value, model=Supplement, user=user)
8888
except ValidationError:
8989
# if it's an edit, don't allow someone to edit a log to a stack
9090
if self.instance:
9191
raise
9292

9393
# we allow for supplement_stack_uuid to also be passed in here, a bit of a hack
94-
validate_model_uuid(SupplementStack, uuid=value, user=user)
94+
validate_model_uuid(uuid=value, model=SupplementStack, user=user)
9595

9696
return value
9797

open/core/betterself/serializers/supplement_stack_composition_serializers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,12 @@ class Meta:
4646

4747
def validate_supplement_uuid(self, value):
4848
user = self.context["request"].user
49-
validate_model_uuid(Supplement, uuid=value, user=user)
49+
validate_model_uuid(uuid=value, model=Supplement, user=user)
5050
return value
5151

5252
def validate_stack_uuid(self, value):
5353
user = self.context["request"].user
54-
validate_model_uuid(SupplementStack, uuid=value, user=user)
54+
validate_model_uuid(uuid=value, model=SupplementStack, user=user)
5555
return value
5656

5757
def validate(self, validated_data):

open/core/betterself/serializers/validators.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,25 @@
33

44
from open.core.betterself.models.activity import Activity
55
from open.core.betterself.models.ingredient_composition import IngredientComposition
6+
from open.core.betterself.models.supplement import Supplement
7+
from functools import partial
68

79

810
def ingredient_composition_uuid_validator(uuid):
9-
validate_model_uuid(IngredientComposition, uuid)
11+
validate_model_uuid(uuid, IngredientComposition)
1012
return uuid
1113

1214

13-
def validate_model_uuid(model, uuid, user=None):
15+
def supplement_uuid_validator(uuid):
16+
validate_model_uuid(uuid, Supplement)
17+
return uuid
18+
19+
20+
def generic_model_uuid_validator(model):
21+
return partial(validate_model_uuid, model=model)
22+
23+
24+
def validate_model_uuid(uuid, model, user=None):
1425
try:
1526
if user:
1627
model.objects.get(uuid=uuid, user=user)
@@ -29,5 +40,5 @@ def validate_activity_uuid(self, value):
2940
if self.context["request"]:
3041
user = self.context["request"].user
3142

32-
validate_model_uuid(Activity, uuid=value, user=user)
43+
validate_model_uuid(uuid=value, model=Activity, user=user)
3344
return value
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from rest_framework.exceptions import ValidationError
2+
from test_plus import TestCase
3+
4+
from open.core.betterself.factories import SupplementFactory
5+
from open.core.betterself.models.supplement import Supplement
6+
from open.core.betterself.serializers.validators import generic_model_uuid_validator
7+
import uuid
8+
9+
"""
10+
dpy test open.core.betterself.tests.test_validators --keepdb
11+
"""
12+
13+
14+
class TestValidators(TestCase):
15+
def test_partial_validator_with_supplement_uuid(self):
16+
supplement = SupplementFactory()
17+
supplement_uuid = str(supplement.uuid)
18+
19+
validator = generic_model_uuid_validator(Supplement)
20+
21+
result = validator(supplement_uuid)
22+
# if it's valid, it doesn't return anything
23+
self.assertIsNone(result)
24+
25+
# bad uuid, it should trip
26+
random_uuid = str(uuid.uuid4())
27+
with self.assertRaises(ValidationError):
28+
validator(random_uuid)
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
from dateutil import relativedelta
2+
from django.contrib.auth import get_user_model
3+
from rest_framework.reverse import reverse
4+
5+
from open.core.betterself.constants import BetterSelfResourceConstants
6+
from open.core.betterself.factories import (
7+
SleepLogFactory,
8+
SupplementFactory,
9+
SupplementLogFactory,
10+
DailyProductivityLogFactory,
11+
)
12+
from open.core.betterself.tests.mixins.resource_mixin import BaseTestCase
13+
from open.core.betterself.utilities.user_date_utilities import (
14+
serialize_date_to_user_localized_datetime,
15+
)
16+
from open.users.factories import UserFactory
17+
from open.utilities.date_and_time import (
18+
get_utc_now,
19+
yyyy_mm_dd_format_1,
20+
)
21+
22+
User = get_user_model()
23+
24+
"""
25+
python manage.py test --pattern="*test_aggregrate_views.py" --keepdb
26+
dpy test open.core.betterself.tests.views.test_aggregrate_views.TestAggregateView
27+
"""
28+
29+
30+
class TestAggregateView(BaseTestCase):
31+
@classmethod
32+
def setUpTestData(cls):
33+
user_1 = UserFactory()
34+
user_2 = UserFactory()
35+
36+
cls.end_period = get_utc_now()
37+
cls.end_period_date_string = cls.end_period.date().strftime(yyyy_mm_dd_format_1)
38+
39+
supplements = SupplementFactory.create_batch(10, user=user_1)
40+
41+
for index in range(100):
42+
# simulate some missing data
43+
if index % 5 == 0 and index != 0:
44+
continue
45+
46+
date_to_use = cls.end_period - relativedelta.relativedelta(days=index)
47+
SleepLogFactory(end_time=date_to_use, user=user_1)
48+
49+
for supplement in supplements:
50+
SupplementLogFactory.create_batch(
51+
2, user=user_1, supplement=supplement, time=date_to_use
52+
)
53+
54+
cls.user_1_id = user_1.id
55+
cls.user_2_id = user_2.id
56+
57+
for index in range(100):
58+
# simulate some missing data
59+
if index % 5 == 0 and index != 0:
60+
continue
61+
62+
date_to_use = cls.end_period - relativedelta.relativedelta(days=index)
63+
DailyProductivityLogFactory(user=user_1, date=date_to_use)
64+
65+
# add some random data to user_2 also to make sure no leaking
66+
DailyProductivityLogFactory(user=user_2, date=date_to_use)
67+
68+
def test_url(self):
69+
url = reverse(BetterSelfResourceConstants.AGGREGATE)
70+
71+
kwargs = {"start_date": "2020-01-02", "end_date": "2020-01-02"}
72+
73+
response = self.client_1.post(url, data=kwargs)
74+
self.assertEqual(response.status_code, 200, response.data)
75+
76+
# nothing in kwargs, should have no supplements data
77+
self.assertNotIn("supplements", response.data)
78+
79+
def test_url_with_supplements_requested(self):
80+
url = reverse(BetterSelfResourceConstants.AGGREGATE)
81+
82+
start_date = "2020-01-02"
83+
84+
supplements = SupplementFactory.create_batch(2, user=self.user_1)
85+
start_time = serialize_date_to_user_localized_datetime(
86+
start_date, user=self.user_1
87+
)
88+
89+
for supplement in supplements:
90+
SupplementLogFactory(
91+
user=self.user_1, supplement=supplement, time=start_time
92+
)
93+
94+
supplement_uuids = [str(supplement.uuid) for supplement in supplements]
95+
96+
kwargs = {
97+
"start_date": "2020-01-01",
98+
"end_date": "2020-01-02",
99+
"supplement_uuids": supplement_uuids,
100+
}
101+
102+
response = self.client_1.post(url, data=kwargs, format="json")
103+
self.assertEqual(response.status_code, 200, response.data)
104+
105+
self.assertIsNotNone(response.data["supplements"])

open/core/betterself/tests/views/test_supplement_stack_views.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,18 @@ class SupplementStackTestGetUpdateView(
4646
def test_get_view_with_supplement_compositions(self):
4747
stack = SupplementStackFactory(user=self.user_1)
4848
compositions_to_create = 3
49-
SupplementStackCompositionFactory.create_batch(
49+
created_comps = SupplementStackCompositionFactory.create_batch(
5050
compositions_to_create, stack=stack, user=self.user_1
5151
)
5252

53+
# sometimes the randomness makes it not quite equal to how many we wanted to create
54+
created_comps_length = len(created_comps)
55+
5356
url = stack.get_update_url()
5457
response = self.client_1.get(url)
5558

5659
self.assertIsNotNone(response.data["compositions"])
57-
self.assertEqual(compositions_to_create, len(response.data["compositions"]))
60+
self.assertEqual(created_comps_length, len(response.data["compositions"]))
5861

5962
def test_update_stack_name(self):
6063
stack = SupplementStackFactory(user=self.user_1)

open/core/betterself/urls.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
ActivityGetUpdateView,
1010
ActivityCreateListView,
1111
)
12+
from open.core.betterself.views.aggregrate_views import AggregateView
1213
from open.core.betterself.views.daily_productivity_log_views import (
1314
DailyProductivityLogCreateListView,
1415
DailyProductivityLogGetUpdateView,
@@ -202,4 +203,9 @@
202203
view=DailyReviewView.as_view(),
203204
name=RESOURCES.DAILY_REVIEW,
204205
),
206+
path(
207+
f"{RESOURCES.AGGREGATE}/",
208+
view=AggregateView.as_view(),
209+
name=RESOURCES.AGGREGATE,
210+
),
205211
]

open/core/betterself/utilities/history_overview_utilities.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
FOOD_LOG_TYPE = "Food"
5757

5858

59-
def get_overview_supplements_data(user, start_period, end_period):
59+
def get_overview_supplements_data(user, start_period, end_period, supplements=None):
6060
response = {
6161
"start_period": start_period.date().isoformat(),
6262
"end_period": end_period.date().isoformat(),

open/core/betterself/utilities/user_date_utilities.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
from django.http import Http404
44

55

6-
def serialize_date_to_user_localized_date(date, user):
7-
try:
8-
date = datetime.strptime(date, "%Y-%m-%d").date()
9-
except ValueError:
10-
raise Http404
6+
def serialize_date_to_user_localized_datetime(date, user):
7+
if isinstance(date, str):
8+
try:
9+
date = datetime.strptime(date, "%Y-%m-%d").date()
10+
except ValueError:
11+
raise Http404
12+
1113
start_period = date
1214
start_period = datetime(
1315
year=start_period.year,
@@ -16,3 +18,23 @@ def serialize_date_to_user_localized_date(date, user):
1618
tzinfo=user.timezone,
1719
)
1820
return start_period
21+
22+
23+
def serialize_end_date_to_user_localized_datetime(date, user):
24+
if isinstance(date, str):
25+
try:
26+
date = datetime.strptime(date, "%Y-%m-%d").date()
27+
except ValueError:
28+
raise Http404
29+
30+
end_period = date
31+
end_period = datetime(
32+
year=end_period.year,
33+
month=end_period.month,
34+
day=end_period.day,
35+
hour=23,
36+
minute=59,
37+
second=59,
38+
tzinfo=user.timezone,
39+
)
40+
return end_period

0 commit comments

Comments
 (0)