Changes in / [3357e47:1183d5a] in OpenWorkouts-current
- Location:
- ow
- Files:
-
- 6 edited
Legend:
- Unmodified
- Added
- Removed
-
ow/models/user.py
r3357e47 r1183d5a 9 9 10 10 from ow.catalog import get_catalog, reindex_object 11 from ow.utilities import get_week_days , get_month_week_number11 from ow.utilities import get_week_days 12 12 13 13 … … 77 77 reindex_object(catalog, workout) 78 78 79 def workouts(self, year=None, month=None , week=None):79 def workouts(self, year=None, month=None): 80 80 """ 81 81 Return this user workouts, sorted by date, from newer to older … … 84 84 if year: 85 85 workouts = [w for w in workouts if w.start.year == year] 86 if month: 87 workouts = [w for w in workouts if w.start.month == month] 88 if week: 89 week = int(week) 90 workouts = [ 91 w for w in workouts if w.start.isocalendar()[1] == week] 86 if month: 87 workouts = [w for w in workouts if w.start.month == month] 92 88 workouts = sorted(workouts, key=attrgetter('start')) 93 89 workouts.reverse() … … 292 288 293 289 return stats 294 295 @property296 def weekly_year_stats(self):297 """298 Return per-week stats for the last 12 months299 """300 # set the boundaries for looking for workouts afterwards,301 # we need the current date as the "end date" and one year302 # ago from that date. Then we set the start at the first303 # day of that month.304 end = datetime.now(timezone.utc)305 start = (end - timedelta(days=365)).replace(day=1)306 307 stats = {}308 309 # first initialize the stats dict310 for days in range((end - start).days):311 day = (start + timedelta(days=days)).date()312 week = day.isocalendar()[1]313 month_week = get_month_week_number(day)314 key = (day.year, day.month, week, month_week)315 if key not in stats.keys():316 stats[key] = {317 'workouts': 0,318 'time': timedelta(0),319 'distance': Decimal(0),320 'elevation': Decimal(0),321 'sports': {}322 }323 324 # now loop over the workouts, filtering and then adding stats325 # to the proper place326 for workout in self.workouts():327 if start.date() <= workout.start.date() <= end.date():328 # less typing, avoid long lines329 start_date = workout.start.date()330 week = start_date.isocalendar()[1]331 month_week = get_month_week_number(start_date)332 week = stats[(start_date.year,333 start_date.month,334 week,335 month_week)]336 337 week['workouts'] += 1338 week['time'] += workout.duration or timedelta(seconds=0)339 week['distance'] += workout.distance or Decimal(0)340 week['elevation'] += workout.uphill or Decimal(0)341 if workout.sport not in week['sports']:342 week['sports'][workout.sport] = {343 'workouts': 0,344 'time': timedelta(seconds=0),345 'distance': Decimal(0),346 'elevation': Decimal(0),347 }348 week['sports'][workout.sport]['workouts'] += 1349 week['sports'][workout.sport]['time'] += (350 workout.duration or timedelta(0))351 week['sports'][workout.sport]['distance'] += (352 workout.distance or Decimal(0))353 week['sports'][workout.sport]['elevation'] += (354 workout.uphill or Decimal(0))355 356 return stats -
ow/static/js/ow.js
r3357e47 r1183d5a 236 236 var chart_selector = spec.chart_selector, 237 237 filters_selector = spec.filters_selector, 238 switcher_selector = spec.switcher_selector, 239 urls = spec.urls, 238 url = spec.url, 240 239 current_month = spec.current_month, 241 current_week = spec.current_week, 242 y_axis_labels = spec.y_axis_labels, 243 filter_by = spec.filter_by, 244 url = spec.url; 240 y_axis_labels = spec.y_axis_labels; 245 241 246 242 // Helpers … … 260 256 }; 261 257 262 function get_name_for_x(d) {263 if (d.week == undefined || d.week == 0) {264 return d.name;265 }266 else {267 return d.id.split('-')[2];268 }269 }270 271 258 // Methods 272 259 var filters_setup = function filters_setup() { 273 260 $(filters_selector).on('click', function(e) { 261 var filter_by = 'distance'; 274 262 e.preventDefault(); 275 263 filter_by = $(this).attr('class').split('-')[1] 276 264 var chart = d3.select(chart_selector); 277 265 chart.selectAll("*").remove(); 278 render(filter_by, url); 279 }); 280 }; 281 282 var switcher_setup = function switcher_setup() { 283 $(switcher_selector).on('click', function(e) { 284 e.preventDefault(); 285 url = $(this).attr('class').split('-')[1] 286 var chart = d3.select(chart_selector); 287 chart.selectAll("*").remove(); 288 render(filter_by, url); 289 }); 290 }; 291 292 var render = function render(filter_by, url) { 266 render(filter_by); 267 }); 268 }; 269 270 var render = function render(filter_by) { 293 271 /* 294 272 Build a d3 bar chart, populated with data from the given url. … … 302 280 y = d3.scaleLinear().rangeRound([height, 0]); 303 281 304 d3.json(url s[url]).then(function (data) {282 d3.json(url).then(function (data) { 305 283 x.domain(data.map(function (d) { 306 return get_name_for_x(d); 307 // return d.name; 284 return d.name; 308 285 })); 309 286 … … 331 308 .enter().append("rect") 332 309 .attr("class", function(d) { 333 var sel_week = current_month + '-' + current_week; 334 if (d.id == current_month || d.id == sel_week){ 335 /* Bar for the currently selected month or week */ 310 if (d.id == current_month){ 336 311 select_x_axis_label(d).attr('style', "font-weight: bold;"); 337 return 'bar current' ;312 return 'bar current' 338 313 } 339 314 else { 340 if (!current_week && d.id.indexOf(current_month) >=0 ) { 341 /* 342 User selected a month, then switched to weekly 343 view, we do highlight all the bars for weeks in 344 that month 345 */ 346 select_x_axis_label(d).attr('style', "font-weight: bold;"); 347 return 'bar current'; 348 } 349 else { 350 /* Non-selected bar */ 351 return 'bar'; 352 } 353 315 return 'bar' 354 316 } 355 317 }) 356 318 .attr("x", function (d) { 357 return x( get_name_for_x(d));319 return x(d.name); 358 320 }) 359 321 .attr("y", function (d) { … … 378 340 }); 379 341 380 if (url == 'monthly') { 381 g.selectAll(".text") 382 .data(data) 383 .enter() 384 .append("text") 385 .attr("class","label") 386 .attr("x", function (d) { 387 return x(get_name_for_x(d)) + x.bandwidth()/2; 388 }) 389 .attr("y", function (d) { 390 /* 391 Get the value for the current bar, then get the maximum 392 value to be displayed in the bar, which is used to 393 calculate the proper position of the label for this bar, 394 relatively to its height (1% above the bar) 395 */ 396 var value = get_y_value(d, filter_by); 397 var max = y.domain()[1]; 398 return y(value + y.domain()[1] * 0.01); 399 }) 400 .text(function(d) { 401 var value = get_y_value(d, filter_by) 402 if ( value > 0) { 403 return value; 404 } 405 }); 406 } 407 408 if (url == 'weekly') { 409 g.selectAll(".tick") 410 .each(function (d, i) { 411 /* 412 Remove from the x-axis tickets those without letters 413 on them (useful for the weekly chart) 414 */ 415 if (d !== parseInt(d, 10)) { 416 if(!d.match(/[a-z]/i)) { 417 this.remove(); 418 } 419 } 420 }); 421 } 342 g.selectAll(".text") 343 .data(data) 344 .enter() 345 .append("text") 346 .attr("class","label") 347 .attr("x", function (d) { 348 return x(d.name) + x.bandwidth()/2; 349 }) 350 .attr("y", function (d) { 351 /* 352 Get the value for the current bar, then get the maximum 353 value to be displayed in the bar, which is used to 354 calculate the proper position of the label for this bar, 355 relatively to its height (1% above the bar) 356 */ 357 var value = get_y_value(d, filter_by); 358 var max = y.domain()[1]; 359 return y(value + y.domain()[1] * 0.01); 360 }) 361 .text(function(d) { 362 var value = get_y_value(d, filter_by) 363 if ( value > 0) { 364 return value; 365 } 366 }); 367 422 368 }); 423 369 }; … … 425 371 var that = {} 426 372 that.filters_setup = filters_setup; 427 that.switcher_setup = switcher_setup;428 373 that.render = render; 429 374 return that -
ow/templates/profile.pt
r3357e47 r1183d5a 72 72 <a href="#" class="js-time" i18n:translate="">time</a> 73 73 <a href="#" class="js-elevation" i18n:translate="">elevation</a> 74 </div>75 <div class="switcher js-switcher">76 <a href="#" class="js-weekly" i18n:translate="">weekly</a>77 <a href="#" class="js-monthly" i18n:translate="">monthly</a>78 74 </div> 79 75 </div> … … 158 154 chart_selector: 'div.js-month-stats svg', 159 155 filters_selector: 'div.js-month-stats div.js-filters a', 160 switcher_selector: 'div.js-month-stats div.js-switcher a', 161 urls: {"monthly": "${request.resource_url(context, 'monthly')}", 162 "weekly": "${request.resource_url(context, 'weekly')}"}, 156 url: "${request.resource_url(context, 'yearly')}", 163 157 current_month: "${current_month}", 164 current_week: "${current_week}",165 158 y_axis_labels: y_axis_labels, 166 filter_by: "distance",167 url: "${'monthly' if current_week is None else 'weekly'}",168 159 }); 169 year_chart.render("distance" , "${'monthly' if current_week is None else 'weekly'}");160 year_chart.render("distance"); 170 161 year_chart.filters_setup(); 171 year_chart.switcher_setup();172 162 </script> 173 163 -
ow/tests/views/test_user.py
r3357e47 r1183d5a 269 269 # profile page for the current day (no workouts avalable) 270 270 response = user_views.profile(john, request) 271 assert len(response.keys()) == 3271 assert len(response.keys()) == 2 272 272 current_month = datetime.now(timezone.utc).strftime('%Y-%m') 273 273 assert response['current_month'] == current_month 274 assert response['current_week'] is None275 274 assert response['workouts'] == [] 276 275 # profile page for a previous date, that has workouts … … 278 277 request.GET['month'] = 8 279 278 response = user_views.profile(john, request) 280 assert len(response.keys()) == 3279 assert len(response.keys()) == 2 281 280 assert response['current_month'] == '2015-08' 282 assert response['current_week'] is None283 assert response['workouts'] == john.workouts(2015, 8)284 # same, passing a week, first on a week without workouts285 request.GET['year'] = 2015286 request.GET['month'] = 8287 request.GET['week'] = 25288 response = user_views.profile(john, request)289 assert len(response.keys()) == 3290 assert response['current_month'] == '2015-08'291 assert response['current_week'] is 25292 assert response['workouts'] == []293 # now in a week with workoutss294 request.GET['year'] = 2015295 request.GET['month'] = 8296 request.GET['week'] = 26297 response = user_views.profile(john, request)298 assert len(response.keys()) == 3299 assert response['current_month'] == '2015-08'300 assert response['current_week'] is 26301 281 assert response['workouts'] == john.workouts(2015, 8) 302 282 -
ow/utilities.py
r3357e47 r1183d5a 2 2 import os 3 3 import logging 4 import calendar5 4 import subprocess 6 5 from datetime import datetime, timedelta … … 235 234 week_days = [first_day + timedelta(days=i) for i in range(7)] 236 235 return week_days 237 238 239 def get_month_week_number(day):240 """241 Given a datetime object (day), return the number of week the day is242 in the current month (week 1, 2, 3, etc)243 """244 weeks = calendar.monthcalendar(day.year, day.month)245 for week in weeks:246 if day.day in week:247 return weeks.index(week)248 return None -
ow/views/user.py
r3357e47 r1183d5a 168 168 year = int(request.GET.get('year', now.year)) 169 169 month = int(request.GET.get('month', now.month)) 170 week = request.GET.get('week', None) 171 return { 172 'workouts': context.workouts(year, month, week), 170 return { 171 'workouts': context.workouts(year, month), 173 172 'current_month': '{year}-{month}'.format( 174 year=str(year), month=str(month).zfill(2)), 175 'current_week': week 173 year=str(year), month=str(month).zfill(2)) 176 174 } 177 175 … … 264 262 context=User, 265 263 permission='view', 266 name=' monthly')264 name='yearly') 267 265 def last_months_stats(context, request): 268 266 """ … … 296 294 charset='utf-8', 297 295 body=json.dumps(json_stats)) 298 299 300 @view_config(301 context=User,302 permission='view',303 name='weekly')304 def last_weeks_stats(context, request):305 """306 Return a json-encoded stream with statistics for the last 12-months, but307 in a per-week basis308 """309 stats = context.weekly_year_stats310 # this sets which month is 2 times in the stats, once this year, once311 # the previous year. We will show it a bit different in the UI (showing312 # the year too to prevent confusion)313 repeated_month = datetime.now(timezone.utc).date().month314 json_stats = []315 for week in stats:316 hms = timedelta_to_hms(stats[week]['time'])317 name = month_name[week[1]][:3]318 if week[1] == repeated_month:319 name += ' ' + str(week[0])320 week_stats = {321 'id': '-'.join(322 [str(week[0]), str(week[1]).zfill(2), str(week[2])]),323 'week': str(week[3]), # the number of week in the current month324 'name': name,325 'time': str(hms[0]).zfill(2),326 'distance': int(round(stats[week]['distance'])),327 'elevation': int(stats[week]['elevation']),328 'workouts': stats[week]['workouts'],329 'url': request.resource_url(330 context, 'profile',331 query={'year': str(week[0]),332 'month': str(week[1]),333 'week': str(week[2])},334 anchor='workouts')335 }336 json_stats.append(week_stats)337 return Response(content_type='application/json',338 charset='utf-8',339 body=json.dumps(json_stats))
Note: See TracChangeset
for help on using the changeset viewer.