Changes in / [3357e47:1183d5a] in OpenWorkouts-current


Ignore:
Location:
ow
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • ow/models/user.py

    r3357e47 r1183d5a  
    99
    1010from ow.catalog import get_catalog, reindex_object
    11 from ow.utilities import get_week_days, get_month_week_number
     11from ow.utilities import get_week_days
    1212
    1313
     
    7777        reindex_object(catalog, workout)
    7878
    79     def workouts(self, year=None, month=None, week=None):
     79    def workouts(self, year=None, month=None):
    8080        """
    8181        Return this user workouts, sorted by date, from newer to older
     
    8484        if year:
    8585            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]
    9288        workouts = sorted(workouts, key=attrgetter('start'))
    9389        workouts.reverse()
     
    292288
    293289        return stats
    294 
    295     @property
    296     def weekly_year_stats(self):
    297         """
    298         Return per-week stats for the last 12 months
    299         """
    300         # set the boundaries for looking for workouts afterwards,
    301         # we need the current date as the "end date" and one year
    302         # ago from that date. Then we set the start at the first
    303         # 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 dict
    310         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 stats
    325         # to the proper place
    326         for workout in self.workouts():
    327             if start.date() <= workout.start.date() <= end.date():
    328                 # less typing, avoid long lines
    329                 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'] += 1
    338                 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'] += 1
    349                 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  
    236236    var chart_selector = spec.chart_selector,
    237237        filters_selector = spec.filters_selector,
    238         switcher_selector = spec.switcher_selector,
    239         urls = spec.urls,
     238        url = spec.url,
    240239        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;
    245241
    246242    // Helpers
     
    260256    };
    261257
    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 
    271258    // Methods
    272259    var filters_setup = function filters_setup() {
    273260        $(filters_selector).on('click', function(e) {
     261            var filter_by = 'distance';
    274262            e.preventDefault();
    275263            filter_by = $(this).attr('class').split('-')[1]
    276264            var chart = d3.select(chart_selector);
    277265            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) {
    293271        /*
    294272          Build a d3 bar chart, populated with data from the given url.
     
    302280            y = d3.scaleLinear().rangeRound([height, 0]);
    303281
    304         d3.json(urls[url]).then(function (data) {
     282        d3.json(url).then(function (data) {
    305283            x.domain(data.map(function (d) {
    306                 return get_name_for_x(d);
    307                 // return d.name;
     284                return d.name;
    308285            }));
    309286
     
    331308                .enter().append("rect")
    332309                .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){
    336311                        select_x_axis_label(d).attr('style', "font-weight: bold;");
    337                         return 'bar current';
     312                        return 'bar current'
    338313                    }
    339314                    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'
    354316                    }
    355317                })
    356318                .attr("x", function (d) {
    357                     return x(get_name_for_x(d));
     319                    return x(d.name);
    358320                })
    359321                .attr("y", function (d) {
     
    378340                });
    379341
    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
    422368        });
    423369    };
     
    425371    var that = {}
    426372    that.filters_setup = filters_setup;
    427     that.switcher_setup = switcher_setup;
    428373    that.render = render;
    429374    return that
  • ow/templates/profile.pt

    r3357e47 r1183d5a  
    7272          <a href="#" class="js-time" i18n:translate="">time</a>
    7373          <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>
    7874        </div>
    7975      </div>
     
    158154         chart_selector: 'div.js-month-stats svg',
    159155         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')}",
    163157         current_month: "${current_month}",
    164          current_week: "${current_week}",
    165158         y_axis_labels: y_axis_labels,
    166          filter_by: "distance",
    167          url: "${'monthly' if current_week is None else 'weekly'}",
    168159     });
    169      year_chart.render("distance", "${'monthly' if current_week is None else 'weekly'}");
     160     year_chart.render("distance");
    170161     year_chart.filters_setup();
    171      year_chart.switcher_setup();
    172162    </script>
    173163
  • ow/tests/views/test_user.py

    r3357e47 r1183d5a  
    269269        # profile page for the current day (no workouts avalable)
    270270        response = user_views.profile(john, request)
    271         assert len(response.keys()) == 3
     271        assert len(response.keys()) == 2
    272272        current_month = datetime.now(timezone.utc).strftime('%Y-%m')
    273273        assert response['current_month'] == current_month
    274         assert response['current_week'] is None
    275274        assert response['workouts'] == []
    276275        # profile page for a previous date, that has workouts
     
    278277        request.GET['month'] = 8
    279278        response = user_views.profile(john, request)
    280         assert len(response.keys()) == 3
     279        assert len(response.keys()) == 2
    281280        assert response['current_month'] == '2015-08'
    282         assert response['current_week'] is None
    283         assert response['workouts'] == john.workouts(2015, 8)
    284         # same, passing a week, first on a week without workouts
    285         request.GET['year'] = 2015
    286         request.GET['month'] = 8
    287         request.GET['week'] = 25
    288         response = user_views.profile(john, request)
    289         assert len(response.keys()) == 3
    290         assert response['current_month'] == '2015-08'
    291         assert response['current_week'] is 25
    292         assert response['workouts'] == []
    293         # now in a week with workoutss
    294         request.GET['year'] = 2015
    295         request.GET['month'] = 8
    296         request.GET['week'] = 26
    297         response = user_views.profile(john, request)
    298         assert len(response.keys()) == 3
    299         assert response['current_month'] == '2015-08'
    300         assert response['current_week'] is 26
    301281        assert response['workouts'] == john.workouts(2015, 8)
    302282
  • ow/utilities.py

    r3357e47 r1183d5a  
    22import os
    33import logging
    4 import calendar
    54import subprocess
    65from datetime import datetime, timedelta
     
    235234    week_days = [first_day + timedelta(days=i) for i in range(7)]
    236235    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 is
    242     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  
    168168    year = int(request.GET.get('year', now.year))
    169169    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),
    173172        '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))
    176174    }
    177175
     
    264262    context=User,
    265263    permission='view',
    266     name='monthly')
     264    name='yearly')
    267265def last_months_stats(context, request):
    268266    """
     
    296294                    charset='utf-8',
    297295                    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, but
    307     in a per-week basis
    308     """
    309     stats = context.weekly_year_stats
    310     # this sets which month is 2 times in the stats, once this year, once
    311     # the previous year. We will show it a bit different in the UI (showing
    312     # the year too to prevent confusion)
    313     repeated_month = datetime.now(timezone.utc).date().month
    314     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 month
    324             '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.