source: OpenWorkouts-current/ow/tests/models/test_user.py @ 04c882d

current
Last change on this file since 04c882d was 04c882d, checked in by Borja Lopez <borja@…>, 5 years ago

(#7) Improvements on the workout totals/stats in the user profile page:

  • Fixed styles for the sport/year dropdowns
  • Added "per workout records" for each year (and all time totals), including maximum distance, time and elevation in a single workout.
  • Property mode set to 100644
File size: 30.3 KB
Line 
1from decimal import Decimal
2from datetime import datetime, timedelta, timezone
3
4import pytest
5from pyramid.security import Allow, Everyone, Deny, ALL_PERMISSIONS
6
7from ow.models.root import OpenWorkouts
8from ow.models.workout import Workout
9from ow.models.user import User
10
11
12class TestUser(object):
13    @pytest.fixture
14    def root(self):
15        root = OpenWorkouts()
16        root['john'] = User(firstname='John', lastname='Doe',
17                            email='john.doe@example.net')
18        root['john'].password = 's3cr3t'
19        return root
20
21    @pytest.fixture
22    def workouts(self):
23        workouts = [Workout(sport='running'),
24                    Workout(sport='cycling'),
25                    Workout(sport='swimming')]
26        return workouts
27
28    def test_user_attrs(self, root):
29        assert root['john'].firstname == 'John'
30        assert root['john'].lastname == 'Doe'
31        assert root['john'].email == 'john.doe@example.net'
32
33    def test__acl__(self, root):
34        uid = str(root['john'].uid)
35        permissions = [
36            (Allow, uid, 'view'),
37            (Allow, uid, 'edit'),
38            (Deny, Everyone, ALL_PERMISSIONS),
39        ]
40        assert root['john'].__acl__() == permissions
41
42    def test__str__(self, root):
43        email = root['john'].email
44        uid = str(root['john'].uid)
45        assert root['john'].__str__() == u'User: ' + uid + ' (' + email + ')'
46
47    def test__repr__(self, root):
48        email = root['john'].email
49        uid = str(root['john'].uid)
50        expected = u'<ow.models.user.User: ' + uid + ' (' + email + ')>'
51        assert root['john'].__repr__() == expected
52
53    def test_fullname(self, root):
54        assert root['john'].fullname == 'John Doe'
55
56    def test_password_is_encrypted(self, root):
57        assert root['john'].password != 's3cr3t'
58
59    def test_check_wrong_password(self, root):
60        assert not root['john'].check_password('badpass')
61
62    def test_check_good_password(self, root):
63        assert root['john'].check_password('s3cr3t')
64
65    def test_add_workout(self, root, workouts):
66        # First add all workouts at once
67        for workout in workouts:
68            root['john'].add_workout(workout)
69        # Then check they are there, in the correct position/number
70        for workout in workouts:
71            index = workouts.index(workout) + 1
72            assert root['john'][str(index)] == workout
73
74    def test_workouts(self, root, workouts):
75        # First add all workouts at once
76        for workout in workouts:
77            root['john'].add_workout(workout)
78        # workouts() will return the workouts sorted from newest to oldest
79        workouts.reverse()
80        assert list(root['john'].workouts()) == workouts
81        assert list(root['john'].workout_ids()) == ['1', '2', '3']
82        assert root['john'].num_workouts == len(workouts)
83
84    def test_favorite_sport(self, root):
85        assert root['john'].favorite_sport is None
86        # add a cycling workout
87        workout = Workout(
88            sport='cycling',
89            start=datetime.now(timezone.utc),
90            duration=timedelta(minutes=120),
91            distance=66,
92        )
93        root['john'].add_workout(workout)
94        assert root['john'].favorite_sport == 'cycling'
95        # add a running workout, both sports have same amount of workouts,
96        # favorite is picked up reversed alphabetically
97        workout = Workout(
98            sport='running',
99            start=datetime.now(timezone.utc),
100            duration=timedelta(minutes=45),
101            distance=5,
102        )
103        root['john'].add_workout(workout)
104        assert root['john'].favorite_sport == 'running'
105        # add another cycling workout, now that is the favorite sport
106        workout = Workout(
107            sport='cycling',
108            start=datetime.now(timezone.utc),
109            duration=timedelta(minutes=60),
110            distance=30,
111        )
112        root['john'].add_workout(workout)
113        assert root['john'].favorite_sport == 'cycling'
114
115    def test_activity_sports(self, root):
116        assert root['john'].activity_sports == []
117        workout = Workout(
118            sport='cycling',
119            start=datetime.now(timezone.utc),
120            duration=timedelta(minutes=120),
121            distance=66,
122        )
123        root['john'].add_workout(workout)
124        assert root['john'].activity_sports == ['cycling']
125        workout = Workout(
126            sport='running',
127            start=datetime.now(timezone.utc),
128            duration=timedelta(minutes=45),
129            distance=5,
130        )
131        root['john'].add_workout(workout)
132        assert root['john'].activity_sports == ['cycling', 'running']
133
134    def test_activity_years(self, root):
135        assert root['john'].activity_years == []
136        workout = Workout(
137            sport='cycling',
138            start=datetime.now(timezone.utc),
139            duration=timedelta(minutes=120),
140            distance=66,
141        )
142        root['john'].add_workout(workout)
143        assert root['john'].activity_years == [datetime.now(timezone.utc).year]
144        workout = Workout(
145            sport='running',
146            start=datetime(2018, 11, 25, 10, 00, tzinfo=timezone.utc),
147            duration=timedelta(minutes=45),
148            distance=5,
149        )
150        root['john'].add_workout(workout)
151        assert root['john'].activity_years == [
152            datetime.now(timezone.utc).year,
153            2018
154        ]
155
156    def test_activity_months(self, root):
157        # we have to pass a year parameter
158        with pytest.raises(TypeError):
159            root['john'].activity_months()
160        now = datetime.now(timezone.utc)
161        assert root['john'].activity_months(now.year) == []
162        workout = Workout(
163            sport='cycling',
164            start=datetime.now(timezone.utc),
165            duration=timedelta(minutes=120),
166            distance=66,
167        )
168        root['john'].add_workout(workout)
169        assert root['john'].activity_months(now.year) == [now.month]
170        assert root['john'].activity_months(now.year-1) == []
171        assert root['john'].activity_months(now.year+1) == []
172        workout = Workout(
173            sport='running',
174            start=datetime(2018, 11, 25, 10, 00, tzinfo=timezone.utc),
175            duration=timedelta(minutes=45),
176            distance=5,
177        )
178        root['john'].add_workout(workout)
179        assert root['john'].activity_months(now.year) == [now.month]
180        assert root['john'].activity_months(2018) == [11]
181        assert root['john'].activity_months(now.year+1) == []
182
183    def test_activity_dates_tree(self, root):
184        # first an empty test
185        assert root['john'].activity_dates_tree == {}
186        # now add a cycling workout in a given date (25/11/2018)
187        workout = Workout(
188            start=datetime(2018, 11, 25, 10, 00, tzinfo=timezone.utc),
189            duration=timedelta(minutes=(60*4)),
190            distance=115,
191            sport='cycling')
192        root['john'].add_workout(workout)
193        assert root['john'].activity_dates_tree == {
194            2018: {11: {'cycling': 1}}
195        }
196        # add a running workout on the same date
197        workout = Workout(
198            start=datetime(2018, 11, 25, 16, 30, tzinfo=timezone.utc),
199            duration=timedelta(minutes=60),
200            distance=12,
201            sport='running')
202        root['john'].add_workout(workout)
203        assert root['john'].activity_dates_tree == {
204            2018: {11: {'cycling': 1, 'running': 1}}
205        }
206        # add a swimming workout on a different date, same year
207        workout = Workout(
208            start=datetime(2018, 8, 15, 11, 30, tzinfo=timezone.utc),
209            duration=timedelta(minutes=30),
210            distance=2,
211            sport='swimming')
212        root['john'].add_workout(workout)
213        assert root['john'].activity_dates_tree == {
214            2018: {8: {'swimming': 1},
215                   11: {'cycling': 1, 'running': 1}}
216        }
217        # now add some more cycling in a different year
218        # add a swimming workout on a different date, same year
219        workout = Workout(
220            start=datetime(2017, 4, 15, 15, 00, tzinfo=timezone.utc),
221            duration=timedelta(minutes=(60*3)),
222            distance=78,
223            sport='cycling')
224        root['john'].add_workout(workout)
225        assert root['john'].activity_dates_tree == {
226            2017: {4: {'cycling': 1}},
227            2018: {8: {'swimming': 1},
228                   11: {'cycling': 1, 'running': 1}}
229        }
230
231    def test_stats(self, root):
232        expected_no_stats = {
233            'workouts': 0,
234            'time': timedelta(seconds=0),
235            'distance': Decimal(0),
236            'elevation': Decimal(0),
237            'sports': {}
238        }
239        # no stats
240        assert root['john'].stats() == expected_no_stats
241        # add a cycling workout
242        workout = Workout(
243            start=datetime(2018, 11, 25, 10, 00, tzinfo=timezone.utc),
244            duration=timedelta(minutes=(60*4)),
245            distance=115,
246            sport='cycling')
247        root['john'].add_workout(workout)
248        # asking for a different year, future
249        assert root['john'].stats(2019) == expected_no_stats
250        # asking for a different year, past
251        assert root['john'].stats(2016) == expected_no_stats
252        # asking fot the year the workout is in
253        assert root['john'].stats(2018) == {
254            'workouts': 1,
255            'time': timedelta(minutes=(60*4)),
256            'distance': Decimal(115),
257            'elevation': Decimal(0),
258            'sports': {
259                'cycling': {
260                    'workouts': 1,
261                    'time': timedelta(minutes=(60*4)),
262                    'distance': Decimal(115),
263                    'elevation': Decimal(0),
264                }
265            }
266        }
267        # add a second cycling workout
268        workout = Workout(
269            start=datetime(2018, 11, 26, 10, 00, tzinfo=timezone.utc),
270            duration=timedelta(minutes=(60*3)),
271            distance=100,
272            sport='cycling')
273        root['john'].add_workout(workout)
274        assert root['john'].stats(2018) == {
275            'workouts': 2,
276            'time': timedelta(minutes=(60*7)),
277            'distance': Decimal(215),
278            'elevation': Decimal(0),
279            'sports': {
280                'cycling': {
281                    'workouts': 2,
282                    'time': timedelta(minutes=(60*7)),
283                    'distance': Decimal(215),
284                    'elevation': Decimal(0),
285                }
286            }
287        }
288        # add a running workout
289        workout = Workout(
290            start=datetime(2018, 11, 26, 16, 00, tzinfo=timezone.utc),
291            duration=timedelta(minutes=(60)),
292            distance=10,
293            sport='running')
294        root['john'].add_workout(workout)
295        assert root['john'].stats(2018) == {
296            'workouts': 3,
297            'time': timedelta(minutes=(60*8)),
298            'distance': Decimal(225),
299            'elevation': Decimal(0),
300            'sports': {
301                'cycling': {
302                    'workouts': 2,
303                    'time': timedelta(minutes=(60*7)),
304                    'distance': Decimal(215),
305                    'elevation': Decimal(0),
306                },
307                'running': {
308                    'workouts': 1,
309                    'time': timedelta(minutes=(60)),
310                    'distance': Decimal(10),
311                    'elevation': Decimal(0),
312                }
313            }
314        }
315        # ensure the stats for future/past years did not change after
316        # adding those workouts
317        assert root['john'].stats(2019) == expected_no_stats
318        assert root['john'].stats(2016) == expected_no_stats
319
320    def test_get_week_stats(self, root):
321        expected_no_stats_per_day = {
322            'workouts': 0,
323            'time': timedelta(0),
324            'distance': Decimal(0),
325            'elevation': Decimal(0),
326            'sports': {}
327        }
328
329        expected_no_stats = {}
330        for i in range(19, 26):
331            day = datetime(2018, 11, i, 10, 00, tzinfo=timezone.utc)
332            expected_no_stats[day] = expected_no_stats_per_day
333
334        day = datetime(2018, 11, 25, 10, 00, tzinfo=timezone.utc)
335        assert root['john'].get_week_stats(day) == expected_no_stats
336
337        # add a cycling workout
338        workout = Workout(
339            start=datetime(2018, 11, 25, 10, 00, tzinfo=timezone.utc),
340            duration=timedelta(minutes=(60*4)),
341            distance=115,
342            sport='cycling')
343        root['john'].add_workout(workout)
344
345        # check a week in the future
346        day = datetime(2019, 11, 25, 10, 00, tzinfo=timezone.utc)
347        week_stats = root['john'].get_week_stats(day)
348        for day in week_stats:
349            assert week_stats[day] == expected_no_stats_per_day
350
351        # check a week in the past
352        day = datetime(2017, 11, 25, 10, 00, tzinfo=timezone.utc)
353        week_stats = root['john'].get_week_stats(day)
354        for day in week_stats:
355            assert week_stats[day] == expected_no_stats_per_day
356
357        # Check the week where the workout is
358        day = datetime(2018, 11, 25, 10, 00, tzinfo=timezone.utc)
359        week_stats = root['john'].get_week_stats(day)
360        for day in week_stats:
361            if day.day == 25:
362                # this is the day where we have a workout
363                assert week_stats[day] == {
364                    'workouts': 1,
365                    'time': timedelta(minutes=(60*4)),
366                    'distance': Decimal(115),
367                    'elevation': Decimal(0),
368                    'sports': {
369                        'cycling': {
370                            'workouts': 1,
371                            'time': timedelta(minutes=(60*4)),
372                            'distance': Decimal(115),
373                            'elevation': Decimal(0)
374                        }
375                    }
376                }
377            else:
378                # day without workout
379                assert week_stats[day] == expected_no_stats_per_day
380
381        # add a second cycling workout
382        workout = Workout(
383            start=datetime(2018, 11, 23, 10, 00, tzinfo=timezone.utc),
384            duration=timedelta(minutes=(60*3)),
385            distance=100,
386            sport='cycling')
387        root['john'].add_workout(workout)
388        day = datetime(2018, 11, 25, 10, 00, tzinfo=timezone.utc)
389        week_stats = root['john'].get_week_stats(day)
390        for day in week_stats:
391            if day.day == 25:
392                # this is the day where we have a workout
393                assert week_stats[day] == {
394                    'workouts': 1,
395                    'time': timedelta(minutes=(60*4)),
396                    'distance': Decimal(115),
397                    'elevation': Decimal(0),
398                    'sports': {
399                        'cycling': {
400                            'workouts': 1,
401                            'time': timedelta(minutes=(60*4)),
402                            'distance': Decimal(115),
403                            'elevation': Decimal(0)
404                        }
405                    }
406                }
407            elif day.day == 23:
408                # this is the day where we have a workout
409                assert week_stats[day] == {
410                    'workouts': 1,
411                    'time': timedelta(minutes=(60*3)),
412                    'distance': Decimal(100),
413                    'elevation': Decimal(0),
414                    'sports': {
415                        'cycling': {
416                            'workouts': 1,
417                            'time': timedelta(minutes=(60*3)),
418                            'distance': Decimal(100),
419                            'elevation': Decimal(0)
420                        }
421                    }
422                }
423            else:
424                # day without workout
425                assert week_stats[day] == expected_no_stats_per_day
426
427    def test_week_stats(self, root):
428        expected_no_stats_per_day = {
429            'workouts': 0,
430            'time': timedelta(0),
431            'distance': Decimal(0),
432            'elevation': Decimal(0),
433            'sports': {}
434        }
435
436        # no workouts for the current week (this tests is for coverage
437        # purposes mostly, as the main logic is tested in test_get_week_stats)
438        day = datetime.now(timezone.utc)
439        week_stats = root['john'].get_week_stats(day)
440        for day in week_stats:
441            assert week_stats[day] == expected_no_stats_per_day
442
443    def test_week_totals(self, root):
444        # no data, empty totals
445        assert root['john'].week_totals == {
446            'distance': Decimal(0),
447            'time': timedelta(0)
448        }
449
450    def test_yearly_stats(self, root):
451        expected_no_stats_per_month = {
452            'workouts': 0,
453            'time': timedelta(0),
454            'distance': Decimal(0),
455            'elevation': Decimal(0),
456            'sports': {}
457        }
458
459        yearly_stats = root['john'].yearly_stats
460        for month, stats in yearly_stats.items():
461            assert stats == expected_no_stats_per_month
462
463        # add a cycling workout
464        start_date = datetime.now(timezone.utc)
465        workout = Workout(
466            start=start_date,
467            duration=timedelta(minutes=(60*4)),
468            uphill=1200,
469            distance=115,
470            sport='cycling')
471        root['john'].add_workout(workout)
472
473        yearly_stats = root['john'].yearly_stats
474        for month, stats in yearly_stats.items():
475            if month == (start_date.year, start_date.month):
476                assert stats == {
477                    'workouts': 1,
478                    'time': timedelta(minutes=(60*4)),
479                    'distance': Decimal(115),
480                    'elevation': Decimal(1200),
481                    'sports': {
482                        'cycling': {'distance': Decimal(115),
483                                    'elevation': Decimal(1200),
484                                    'time': timedelta(minutes=(60*4)),
485                                    'workouts': 1}
486                    }
487                }
488            else:
489                assert stats == expected_no_stats_per_month
490
491        # add a second cycling workout
492        workout = Workout(
493            start=start_date,
494            duration=timedelta(minutes=(30)),
495            uphill=500,
496            distance=20,
497            sport='cycling')
498        root['john'].add_workout(workout)
499
500        yearly_stats = root['john'].yearly_stats
501        for month, stats in yearly_stats.items():
502            if month == (start_date.year, start_date.month):
503                assert stats == {
504                    'workouts': 2,
505                    'time': timedelta(minutes=((60*4)+30)),
506                    'distance': Decimal(115+20),
507                    'elevation': Decimal(1200+500),
508                    'sports': {
509                        'cycling': {'distance': Decimal(115+20),
510                                    'elevation': Decimal(1200+500),
511                                    'time': timedelta(minutes=((60*4)+30)),
512                                    'workouts': 2}
513                    }
514                }
515            else:
516                assert stats == expected_no_stats_per_month
517
518        # add a running workout
519        workout = Workout(
520            start=start_date,
521            duration=timedelta(minutes=(60)),
522            uphill=200,
523            distance=5,
524            sport='running')
525        root['john'].add_workout(workout)
526
527        yearly_stats = root['john'].yearly_stats
528        for month, stats in yearly_stats.items():
529            if month == (start_date.year, start_date.month):
530                assert stats == {
531                    'workouts': 3,
532                    'time': timedelta(minutes=((60*4)+30+60)),
533                    'distance': Decimal(115+20+5),
534                    'elevation': Decimal(1200+500+200),
535                    'sports': {
536                        'cycling': {'distance': Decimal(115+20),
537                                    'elevation': Decimal(1200+500),
538                                    'time': timedelta(minutes=((60*4)+30)),
539                                    'workouts': 2},
540                        'running': {'distance': Decimal(5),
541                                    'elevation': Decimal(200),
542                                    'time': timedelta(minutes=(60)),
543                                    'workouts': 1}
544                    }
545                }
546            else:
547                assert stats == expected_no_stats_per_month
548
549    def test_weekly_year_stats(self, root):
550        expected_no_stats_per_week = {
551            'workouts': 0,
552            'time': timedelta(0),
553            'distance': Decimal(0),
554            'elevation': Decimal(0),
555            'sports': {}
556        }
557
558        weekly_year_stats = root['john'].weekly_year_stats
559        for week, stats in weekly_year_stats.items():
560            assert stats == expected_no_stats_per_week
561
562        # add a cycling workout
563        start_date = datetime.now(timezone.utc)
564        workout = Workout(
565            start=start_date,
566            duration=timedelta(minutes=(60*4)),
567            uphill=1200,
568            distance=115,
569            sport='cycling')
570        root['john'].add_workout(workout)
571
572        weekly_year_stats = root['john'].weekly_year_stats
573        workout_week = (start_date.year, start_date.month,
574                        start_date.isocalendar()[1])
575        for week, stats in weekly_year_stats.items():
576            if week[:3] == workout_week:
577                assert stats == {
578                    'workouts': 1,
579                    'time': timedelta(minutes=(60*4)),
580                    'distance': Decimal(115),
581                    'elevation': Decimal(1200),
582                    'sports': {
583                        'cycling': {'distance': Decimal(115),
584                                    'elevation': Decimal(1200),
585                                    'time': timedelta(minutes=(60*4)),
586                                    'workouts': 1}
587                    }
588                }
589            else:
590                assert stats == expected_no_stats_per_week
591
592        # add a second cycling workout
593        workout = Workout(
594            start=start_date,
595            duration=timedelta(minutes=(30)),
596            uphill=500,
597            distance=20,
598            sport='cycling')
599        root['john'].add_workout(workout)
600
601        weekly_year_stats = root['john'].weekly_year_stats
602        workout_week = (start_date.year, start_date.month,
603                        start_date.isocalendar()[1])
604        for week, stats in weekly_year_stats.items():
605            if week[:3] == workout_week:
606                assert stats == {
607                    'workouts': 2,
608                    'time': timedelta(minutes=((60*4)+30)),
609                    'distance': Decimal(115+20),
610                    'elevation': Decimal(1200+500),
611                    'sports': {
612                        'cycling': {'distance': Decimal(115+20),
613                                    'elevation': Decimal(1200+500),
614                                    'time': timedelta(minutes=((60*4)+30)),
615                                    'workouts': 2}
616                    }
617                }
618            else:
619                assert stats == expected_no_stats_per_week
620
621        # add a running workout
622        workout = Workout(
623            start=start_date,
624            duration=timedelta(minutes=(60)),
625            uphill=200,
626            distance=5,
627            sport='running')
628        root['john'].add_workout(workout)
629
630        weekly_year_stats = root['john'].weekly_year_stats
631        workout_week = (start_date.year, start_date.month,
632                        start_date.isocalendar()[1])
633        for week, stats in weekly_year_stats.items():
634            if week[:3] == workout_week:
635                assert stats == {
636                    'workouts': 3,
637                    'time': timedelta(minutes=((60*4)+30+60)),
638                    'distance': Decimal(115+20+5),
639                    'elevation': Decimal(1200+500+200),
640                    'sports': {
641                        'cycling': {'distance': Decimal(115+20),
642                                    'elevation': Decimal(1200+500),
643                                    'time': timedelta(minutes=((60*4)+30)),
644                                    'workouts': 2},
645                        'running': {'distance': Decimal(5),
646                                    'elevation': Decimal(200),
647                                    'time': timedelta(minutes=(60)),
648                                    'workouts': 1}
649                    }
650                }
651            else:
652                assert stats == expected_no_stats_per_week
653
654    def test_sport_totals(self, root):
655        # user has no workouts, so no totals
656        assert root['john'].sport_totals() == {
657            'workouts': 0,
658            'time': timedelta(0),
659            'distance': Decimal(0),
660            'elevation': Decimal(0),
661            'max_time': timedelta(0),
662            'max_time_wid': None,
663            'max_distance': Decimal(0),
664            'max_distance_wid': None,
665            'max_elevation': Decimal(0),
666            'max_elevation_wid': None
667        }
668        # add a cycling workout happening now
669        workout = Workout(
670            sport='cycling',
671            start=datetime.now(timezone.utc),
672            duration=timedelta(minutes=120),
673            distance=Decimal(66),
674        )
675        root['john'].add_workout(workout)
676        cycling_wid = workout.workout_id
677        # only one workout, one sport, so the default will show totals
678        # for that sport
679        assert root['john'].sport_totals() == {
680            'workouts': 1,
681            'time': timedelta(minutes=120),
682            'distance': Decimal(66),
683            'elevation': Decimal(0),
684            'max_time': timedelta(minutes=120),
685            'max_time_wid': cycling_wid,
686            'max_distance': Decimal(66),
687            'max_distance_wid': cycling_wid,
688            'max_elevation': Decimal(0),
689            'max_elevation_wid': None
690        }
691        # Add a running workout
692        workout = Workout(
693            sport='running',
694            start=datetime(2018, 11, 25, 10, 00, tzinfo=timezone.utc),
695            duration=timedelta(minutes=45),
696            distance=Decimal(5),
697        )
698        root['john'].add_workout(workout)
699        running_wid = workout.workout_id
700        # the favorite sport is running now
701        assert root['john'].sport_totals() == {
702            'workouts': 1,
703            'time': timedelta(minutes=45),
704            'distance': Decimal(5),
705            'elevation': Decimal(0),
706            'max_time': timedelta(minutes=45),
707            'max_time_wid': running_wid,
708            'max_distance': Decimal(5),
709            'max_distance_wid': running_wid,
710            'max_elevation': Decimal(0),
711            'max_elevation_wid': None,
712        }
713        # but we can get the totals for cycling too
714        assert root['john'].sport_totals('cycling') == {
715            'workouts': 1,
716            'time': timedelta(minutes=120),
717            'distance': Decimal(66),
718            'elevation': Decimal(0),
719            'max_time': timedelta(minutes=120),
720            'max_time_wid': cycling_wid,
721            'max_distance': Decimal(66),
722            'max_distance_wid': cycling_wid,
723            'max_elevation': Decimal(0),
724            'max_elevation_wid': None
725        }
726        # adding a new cycling workout, in a different year
727        workout = Workout(
728            sport='cycling',
729            start=datetime(2017, 11, 25, 10, 00, tzinfo=timezone.utc),
730            duration=timedelta(minutes=60),
731            distance=Decimal(32),
732            uphill=Decimal(430)
733        )
734        root['john'].add_workout(workout)
735        second_cycling_wid = workout.workout_id
736        # now cycling is the favorite sport
737        assert root['john'].sport_totals() == {
738            'workouts': 2,
739            'time': timedelta(minutes=180),
740            'distance': Decimal(98),
741            'elevation': Decimal(430),
742            'max_time': timedelta(minutes=120),
743            'max_time_wid': cycling_wid,
744            'max_distance': Decimal(66),
745            'max_distance_wid': cycling_wid,
746            'max_elevation': Decimal(430),
747            'max_elevation_wid': second_cycling_wid
748        }
749        # but we can get running stats too
750        assert root['john'].sport_totals('running') == {
751            'workouts': 1,
752            'time': timedelta(minutes=45),
753            'distance': Decimal(5),
754            'elevation': Decimal(0),
755            'max_time': timedelta(minutes=45),
756            'max_time_wid': running_wid,
757            'max_distance': Decimal(5),
758            'max_distance_wid': running_wid,
759            'max_elevation': Decimal(0),
760            'max_elevation_wid': None,
761        }
762        # there are no running activities for 2016
763        assert root['john'].sport_totals('running', 2016) == {
764            'workouts': 0,
765            'time': timedelta(0),
766            'distance': Decimal(0),
767            'elevation': Decimal(0),
768            'max_time': timedelta(0),
769            'max_time_wid': None,
770            'max_distance': Decimal(0),
771            'max_distance_wid': None,
772            'max_elevation': Decimal(0),
773            'max_elevation_wid': None
774        }
775        # and not activities for cycling in 2016 neither
776        assert root['john'].sport_totals('cycling', 2016) == {
777            'workouts': 0,
778            'time': timedelta(0),
779            'distance': Decimal(0),
780            'elevation': Decimal(0),
781            'max_time': timedelta(0),
782            'max_time_wid': None,
783            'max_distance': Decimal(0),
784            'max_distance_wid': None,
785            'max_elevation': Decimal(0),
786            'max_elevation_wid': None
787        }
788        # and we can get the separate totals for cycling in different years
789        year = datetime.now(timezone.utc).year
790        assert root['john'].sport_totals('cycling', year) == {
791            'workouts': 1,
792            'time': timedelta(minutes=120),
793            'distance': Decimal(66),
794            'elevation': Decimal(0),
795            'max_time': timedelta(minutes=120),
796            'max_time_wid': cycling_wid,
797            'max_distance': Decimal(66),
798            'max_distance_wid': cycling_wid,
799            'max_elevation': Decimal(0),
800            'max_elevation_wid': None
801        }
802        assert root['john'].sport_totals('cycling', 2017) == {
803            'workouts': 1,
804            'time': timedelta(minutes=60),
805            'distance': Decimal(32),
806            'elevation': Decimal(430),
807            'max_time': timedelta(minutes=60),
808            'max_time_wid': second_cycling_wid,
809            'max_distance': Decimal(32),
810            'max_distance_wid': second_cycling_wid,
811            'max_elevation': Decimal(430),
812            'max_elevation_wid': second_cycling_wid
813        }
Note: See TracBrowser for help on using the repository browser.