- Timestamp:
- Jan 15, 2019, 10:13:57 PM (5 years ago)
- Branches:
- current, feature/docs, master
- Children:
- 0c18869
- Parents:
- 9bee49d
- Location:
- ow
- Files:
-
- 6 edited
Legend:
- Unmodified
- Added
- Removed
-
ow/models/user.py
r9bee49d r2d91474 75 75 reindex_object(catalog, workout) 76 76 77 def workouts(self ):77 def workouts(self, year=None, month=None): 78 78 """ 79 79 Return this user workouts, sorted by date, from newer to older 80 80 """ 81 workouts = sorted(self.values(), key=attrgetter('start')) 81 workouts = self.values() 82 if year: 83 workouts = [w for w in workouts if w.start.year == year] 84 if month: 85 workouts = [w for w in workouts if w.start.month == month] 86 workouts = sorted(workouts, key=attrgetter('start')) 82 87 workouts.reverse() 83 88 return workouts … … 89 94 def num_workouts(self): 90 95 return len(self.workout_ids()) 96 97 @property 98 def activity_years(self): 99 return sorted(list(set(w.start.year for w in self.workouts())), 100 reverse=True) 101 102 def activity_months(self, year): 103 months = set( 104 w.start.month for w in self.workouts() if w.start.year == year) 105 return sorted(list(months)) 106 107 @property 108 def activity_dates_tree(self): 109 """ 110 Return a dict containing information about the activity for this 111 user. 112 113 Example: 114 115 { 116 2019: { 117 1: {'cycling': 12, 'running': 1} 118 }, 119 2018: { 120 1: {'cycling': 10, 'running': 3}, 121 2: {'cycling': 14, 'swimming': 5} 122 } 123 } 124 """ 125 tree = {} 126 for workout in self.workouts(): 127 year = workout.start.year 128 month = workout.start.month 129 sport = workout.sport 130 if year not in tree: 131 tree[year] = {} 132 if month not in tree[year]: 133 tree[year][month] = {} 134 if sport not in tree[year][month]: 135 tree[year][month][sport] = 0 136 tree[year][month][sport] += 1 137 return tree -
ow/static/css/main.css
r9bee49d r2d91474 760 760 } 761 761 762 .workout-activity-tree { 763 padding:0; 764 list-style-type: none; 765 transition:all 250ms ease-in-out 766 list-style-type:none; 767 font-size:13px; 768 font-size:.8125rem; 769 color: #959595; 770 } 771 772 .workout-activity-tree a { 773 text-decoration: none; 774 background-color: transparent; 775 outline: 0; 776 color: #959595; 777 } 778 779 780 .workout-activity-tree li a.viewing-year { 781 font-size: 17px; 782 color: #151515; 783 } 784 785 .workout-activity-tree ul.hidden { 786 display: none; 787 } 788 789 .workout-activity-tree-year { 790 list-style-type: none; 791 padding: 0; 792 padding-left: 15px; 793 font-size:13px; 794 font-size:.8125rem; 795 color: #959595; 796 } 797 798 .workout-activity-tree-year a { 799 text-decoration: none; 800 background-color: transparent; 801 outline: 0; 802 color: #959595; 803 } 804 805 806 .workout-activity-tree-year li a.viewing-month { 807 font-size: 17px; 808 color: #151515; 809 } 810 811 .workout-activity-tree-month { 812 list-style-type: none; 813 padding: 0; 814 padding-left: 15px; 815 font-size:13px; 816 font-size:.8125rem 817 color: #959595; 818 } 819 820 .workout-activity-tree-month a { 821 text-decoration: none; 822 background-color: transparent; 823 outline: 0; 824 color: #959595; 825 } 826 827 828 762 829 @media (min-width:800px) { 763 830 .workout-aside { -
ow/templates/dashboard.pt
r9bee49d r2d91474 27 27 <h2 tal:content="context.fullname"></h2> 28 28 29 <tal:r tal:repeat="workout context.workouts()"> 29 <h3> 30 (<tal:n tal:content="len(workouts)"></tal:n>/<tal:n tal:content="context.num_workouts"></tal:n>) <tal:t i18n:translate="">workouts</tal:t> 31 </h3> 32 33 <tal:no-workouts tal:condition="context.num_workouts == 0"> 34 <div class="warning"> 35 <p i18n:translate="">You haven't added any workouts yet</p> 36 <p> 37 <a href="" i18n:translate="" 38 tal:attributes="href request.resource_url(context, 'add-workout')"> 39 Upload one</a> | 40 <a href="" i18n:translate="" 41 tal:attributes="href request.resource_url(context, 'add-workout-manually')"> 42 Add one manually</a> 43 </p> 44 </div> 45 </tal:no-workouts> 46 47 <tal:r tal:repeat="workout workouts"> 30 48 31 49 <article class="workout-resume"> … … 85 103 86 104 <aside class="workout-aside"> 87 <h2>...</h2> 105 <tal:activity_tree tal:condition="context.num_workouts > 0"> 106 <ul class="workout-activity-tree" tal:define="tree context.activity_dates_tree"> 107 <tal:years tal:repeat="year sorted(tree.keys(), reverse=True )"> 108 <li tal:define="is_viewing_year year == viewing_year"> 109 <a href="" tal:content="year" 110 tal:attributes="href request.resource_url(context, query={'year': year}); 111 class 'js-year viewing-year' if is_viewing_year else 'js-year'"> 112 </a> 113 <ul class="workout-activity-tree-year" 114 tal:attributes="class 'workout-activity-tree-year' if is_viewing_year else 'workout-activity-tree-year hidden'"> 115 <tal:months tal:repeat="month sorted(tree[year].keys())"> 116 <li tal:define="is_viewing_month is_viewing_year and month == viewing_month"> 117 <a href="" tal:content="month_name[month]" 118 tal:attributes="href request.resource_url(context, query={'year': year, 'month': month}); 119 class 'viewing-month' if is_viewing_month else ''"> 120 </a> 121 <ul class="workout-activity-tree-month"> 122 <tal:sports tal:repeat="sport sorted(tree[year][month].keys())"> 123 <li> 124 <a href="#"> 125 <tal:sport tal:content="sport"></tal:sport> (<tal:workouts tal:content="tree[year][month][sport]"></tal:workouts>) 126 </a> 127 </li> 128 </tal:sports> 129 </ul> 130 </li> 131 </tal:months> 132 </ul> 133 </li> 134 </tal:years> 135 </ul> 136 </tal:activity_tree> 88 137 </aside> 89 138 -
ow/tests/models/test_user.py
r9bee49d r2d91474 1 from datetime import datetime, timedelta, timezone 2 1 3 import pytest 2 4 from pyramid.security import Allow … … 68 70 assert list(root['john'].workout_ids()) == ['1', '2', '3'] 69 71 assert root['john'].num_workouts == len(workouts) 72 73 def test_activity_dates_tree(self, root): 74 # first an empty test 75 assert root['john'].activity_dates_tree == {} 76 # now add a cycling workout in a given date (25/11/2018) 77 workout = Workout( 78 start=datetime(2018, 11, 25, 10, 00, tzinfo=timezone.utc), 79 duration=timedelta(minutes=(60*4)), 80 distance=115, 81 sport='cycling') 82 root['john'].add_workout(workout) 83 assert root['john'].activity_dates_tree == { 84 2018: {11: {'cycling': 1}} 85 } 86 # add a running workout on the same date 87 workout = Workout( 88 start=datetime(2018, 11, 25, 16, 30, tzinfo=timezone.utc), 89 duration=timedelta(minutes=60), 90 distance=12, 91 sport='running') 92 root['john'].add_workout(workout) 93 assert root['john'].activity_dates_tree == { 94 2018: {11: {'cycling': 1, 'running': 1}} 95 } 96 # add a swimming workout on a different date, same year 97 workout = Workout( 98 start=datetime(2018, 8, 15, 11, 30, tzinfo=timezone.utc), 99 duration=timedelta(minutes=30), 100 distance=2, 101 sport='swimming') 102 root['john'].add_workout(workout) 103 assert root['john'].activity_dates_tree == { 104 2018: {8: {'swimming': 1}, 105 11: {'cycling': 1, 'running': 1}} 106 } 107 # now add some more cycling in a different year 108 # add a swimming workout on a different date, same year 109 workout = Workout( 110 start=datetime(2017, 4, 15, 15, 00, tzinfo=timezone.utc), 111 duration=timedelta(minutes=(60*3)), 112 distance=78, 113 sport='cycling') 114 root['john'].add_workout(workout) 115 assert root['john'].activity_dates_tree == { 116 2017: {4: {'cycling': 1}}, 117 2018: {8: {'swimming': 1}, 118 11: {'cycling': 1, 'running': 1}} 119 } -
ow/tests/views/test_user.py
r9bee49d r2d91474 152 152 request = dummy_request 153 153 response = user_views.dashboard(john, request) 154 assert response == {} 154 assert len(response) == 4 155 assert 'month_name' in response.keys() 156 # this user has a single workout, in 2015 157 assert response['viewing_year'] == 2015 158 assert response['viewing_month'] == 6 159 assert response['workouts'] == [w for w in john.workouts()] 160 161 def test_dashboard_year(self, dummy_request, john): 162 """ 163 Renders the user dashboard for a chosen year. 164 """ 165 request = dummy_request 166 # first test the year for which we know there is a workout 167 request.GET['year'] = 2015 168 response = user_views.dashboard(john, request) 169 assert len(response) == 4 170 assert 'month_name' in response.keys() 171 # this user has a single workout, in 2015 172 assert response['viewing_year'] == 2015 173 assert response['viewing_month'] == 6 174 assert response['workouts'] == [w for w in john.workouts()] 175 # now, a year we know there is no workout info 176 request.GET['year'] = 2000 177 response = user_views.dashboard(john, request) 178 assert len(response) == 4 179 assert 'month_name' in response.keys() 180 # this user has a single workout, in 2015 181 assert response['viewing_year'] == 2000 182 # we have no data for that year and we didn't ask for a certain month, 183 # so the passing value for that is None 184 assert response['viewing_month'] is None 185 assert response['workouts'] == [] 186 187 def test_dashboard_year_month(self, dummy_request, john): 188 """ 189 Renders the user dashboard for a chosen year and month. 190 """ 191 request = dummy_request 192 # first test the year/month for which we know there is a workout 193 request.GET['year'] = 2015 194 request.GET['month'] = 6 195 response = user_views.dashboard(john, request) 196 assert len(response) == 4 197 assert 'month_name' in response.keys() 198 # this user has a single workout, in 2015 199 assert response['viewing_year'] == 2015 200 assert response['viewing_month'] == 6 201 assert response['workouts'] == [w for w in john.workouts()] 202 # now, change month to one without values 203 request.GET['month'] = 2 204 response = user_views.dashboard(john, request) 205 assert len(response) == 4 206 assert 'month_name' in response.keys() 207 # this user has a single workout, in 2015 208 assert response['viewing_year'] == 2015 209 assert response['viewing_month'] == 2 210 assert response['workouts'] == [] 211 # now the month with data, but in a different year 212 request.GET['year'] = 2010 213 request.GET['month'] = 6 214 response = user_views.dashboard(john, request) 215 assert len(response) == 4 216 assert 'month_name' in response.keys() 217 # this user has a single workout, in 2015 218 assert response['viewing_year'] == 2010 219 assert response['viewing_month'] == 6 220 assert response['workouts'] == [] 221 222 def test_dashboard_month(self, dummy_request, john): 223 """ 224 Passing a month without a year when rendering the dashboard. The last 225 year for which workout data is available is assumed 226 """ 227 request = dummy_request 228 # Set a month without workout data 229 request.GET['month'] = 5 230 response = user_views.dashboard(john, request) 231 assert len(response) == 4 232 assert 'month_name' in response.keys() 233 # this user has a single workout, in 2015 234 assert response['viewing_year'] == 2015 235 assert response['viewing_month'] == 5 236 assert response['workouts'] == [] 237 # now a month with data 238 request.GET['month'] = 6 239 response = user_views.dashboard(john, request) 240 assert len(response) == 4 241 assert 'month_name' in response.keys() 242 # this user has a single workout, in 2015 243 assert response['viewing_year'] == 2015 244 assert response['viewing_month'] == 6 245 assert response['workouts'] == [w for w in john.workouts()] 155 246 156 247 def test_profile(self, dummy_request, john): -
ow/views/user.py
r9bee49d r2d91474 1 from calendar import month_name 2 1 3 from pyramid.httpexceptions import HTTPFound 2 4 from pyramid.view import view_config … … 112 114 Render a dashboard for the current user 113 115 """ 114 # Add here some logic 115 return {} 116 # Look at the year we are viewing, if none is passed in the request, 117 # pick up the latest/newer available with activity 118 viewing_year = request.GET.get('year', None) 119 if viewing_year is None: 120 available_years = context.activity_years 121 if available_years: 122 viewing_year = available_years[0] 123 else: 124 # ensure this is an integer 125 viewing_year = int(viewing_year) 126 127 # Same for the month, if there is a year set 128 viewing_month = None 129 if viewing_year: 130 viewing_month = request.GET.get('month', None) 131 if viewing_month is None: 132 available_months = context.activity_months(viewing_year) 133 if available_months: 134 # we pick up the latest month available for the year, 135 # which means the current month in the current year 136 viewing_month = available_months[-1] 137 else: 138 # ensure this is an integer 139 viewing_month = int(viewing_month) 140 141 # pick up the workouts to be shown in the dashboard 142 workouts = context.workouts(viewing_year, viewing_month) 143 144 return { 145 'month_name': month_name, 146 'viewing_year': viewing_year, 147 'viewing_month': viewing_month, 148 'workouts': workouts 149 } 116 150 117 151
Note: See TracChangeset
for help on using the changeset viewer.