Changeset 02048a6 in OpenWorkouts-current


Ignore:
Timestamp:
Jan 16, 2019, 11:52:22 AM (5 years ago)
Author:
Borja Lopez <borja@…>
Branches:
current, feature/docs, master
Children:
816820d, c6219ed
Parents:
7388b68 (diff), ad5759b (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
Message:

Merged changes from darcs

Location:
ow
Files:
9 edited

Legend:

Unmodified
Added
Removed
  • ow/models/user.py

    r7388b68 r02048a6  
    7575        reindex_object(catalog, workout)
    7676
    77     def workouts(self):
     77    def workouts(self, year=None, month=None):
    7878        """
    7979        Return this user workouts, sorted by date, from newer to older
    8080        """
    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'))
    8287        workouts.reverse()
    8388        return workouts
     
    8994    def num_workouts(self):
    9095        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

    r7388b68 r02048a6  
    11img,legend {
    2         border:0
     2    border:0
    33}
    44
    55legend,td,th {
    6         padding:0
     6    padding:0
    77}
    88
    99.add-workout,.button,sub,sup {
    10         position:relative
     10    position:relative
    1111}
    1212
    1313.button,.header-content a,.workout-resume ul a,.workout-title a {
    14         text-decoration:none
     14    text-decoration:none
    1515}
    1616
    1717@font-face {
    18         font-family:'Open Sans';
    19         font-style:normal;
    20         font-weight:300;
    21         src:local('Open Sans Light'),local('OpenSans-Light'),url(http://fonts.gstatic.com/s/opensans/v13/DXI1ORHCpsQm3Vp6mXoaTYnF5uFdDttMLvmWuJdhhgs.ttf) format('truetype')
     18    font-family:'Open Sans';
     19    font-style:normal;
     20    font-weight:300;
     21    src:local('Open Sans Light'),local('OpenSans-Light'),url(http://fonts.gstatic.com/s/opensans/v13/DXI1ORHCpsQm3Vp6mXoaTYnF5uFdDttMLvmWuJdhhgs.ttf) format('truetype')
    2222}
    2323
    2424@font-face {
    25         font-family:'Open Sans';
    26         font-style:normal;
    27         font-weight:400;
    28         src:local('Open Sans'),local('OpenSans'),url(http://fonts.gstatic.com/s/opensans/v13/cJZKeOuBrn4kERxqtaUH3aCWcynf_cDxXwCLxiixG1c.ttf) format('truetype')
     25    font-family:'Open Sans';
     26    font-style:normal;
     27    font-weight:400;
     28    src:local('Open Sans'),local('OpenSans'),url(http://fonts.gstatic.com/s/opensans/v13/cJZKeOuBrn4kERxqtaUH3aCWcynf_cDxXwCLxiixG1c.ttf) format('truetype')
    2929}
    3030
    3131@font-face {
    32         font-family:'Open Sans';
    33         font-style:normal;
    34         font-weight:700;
    35         src:local('Open Sans Bold'),local('OpenSans-Bold'),url(http://fonts.gstatic.com/s/opensans/v13/k3k702ZOKiLJc3WVjuplzInF5uFdDttMLvmWuJdhhgs.ttf) format('truetype')
     32    font-family:'Open Sans';
     33    font-style:normal;
     34    font-weight:700;
     35    src:local('Open Sans Bold'),local('OpenSans-Bold'),url(http://fonts.gstatic.com/s/opensans/v13/k3k702ZOKiLJc3WVjuplzInF5uFdDttMLvmWuJdhhgs.ttf) format('truetype')
    3636}
    3737
    3838@font-face {
    39         font-family:'Open Sans';
    40         font-style:normal;
    41         font-weight:800;
    42         src:local('Open Sans Extrabold'),local('OpenSans-Extrabold'),url(http://fonts.gstatic.com/s/opensans/v13/EInbV5DfGHOiMmvb1Xr-honF5uFdDttMLvmWuJdhhgs.ttf) format('truetype')
     39    font-family:'Open Sans';
     40    font-style:normal;
     41    font-weight:800;
     42    src:local('Open Sans Extrabold'),local('OpenSans-Extrabold'),url(http://fonts.gstatic.com/s/opensans/v13/EInbV5DfGHOiMmvb1Xr-honF5uFdDttMLvmWuJdhhgs.ttf) format('truetype')
    4343}
    4444
    4545@font-face {
    46         font-family:'Open Sans';
    47         font-style:italic;
    48         font-weight:300;
    49         src:local('Open Sans Light Italic'),local('OpenSansLight-Italic'),url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxrfB31yxOzP-czbf6AAKCVo.ttf) format('truetype')
     46    font-family:'Open Sans';
     47    font-style:italic;
     48    font-weight:300;
     49    src:local('Open Sans Light Italic'),local('OpenSansLight-Italic'),url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxrfB31yxOzP-czbf6AAKCVo.ttf) format('truetype')
    5050}
    5151
    5252@font-face {
    53         font-family:'Open Sans';
    54         font-style:italic;
    55         font-weight:400;
    56         src:local('Open Sans Italic'),local('OpenSans-Italic'),url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBp0EAVxt0G0biEntp43Qt6E.ttf) format('truetype')
     53    font-family:'Open Sans';
     54    font-style:italic;
     55    font-weight:400;
     56    src:local('Open Sans Italic'),local('OpenSans-Italic'),url(http://fonts.gstatic.com/s/opensans/v13/xjAJXh38I15wypJXxuGMBp0EAVxt0G0biEntp43Qt6E.ttf) format('truetype')
    5757}
    5858
    5959@font-face {
    60         font-family:'Open Sans';
    61         font-style:italic;
    62         font-weight:700;
    63         src:local('Open Sans Bold Italic'),local('OpenSans-BoldItalic'),url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxp_TkvowlIOtbR7ePgFOpF4.ttf) format('truetype')
     60    font-family:'Open Sans';
     61    font-style:italic;
     62    font-weight:700;
     63    src:local('Open Sans Bold Italic'),local('OpenSans-BoldItalic'),url(http://fonts.gstatic.com/s/opensans/v13/PRmiXeptR36kaC0GEAetxp_TkvowlIOtbR7ePgFOpF4.ttf) format('truetype')
    6464}
    6565
    6666html {
    67         font-family:sans-serif;
    68         -ms-text-size-adjust:100%;
    69         -webkit-text-size-adjust:100%;
    70         box-sizing:border-box
     67    font-family:sans-serif;
     68    -ms-text-size-adjust:100%;
     69    -webkit-text-size-adjust:100%;
     70    box-sizing:border-box
    7171}
    7272
    7373article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary {
    74         display:block
     74    display:block
    7575}
    7676
    7777audio,canvas,progress,video {
    78         display:inline-block;
    79         vertical-align:baseline
     78    display:inline-block;
     79    vertical-align:baseline
    8080}
    8181
    8282audio:not([controls]) {
    83         display:none;
    84         height:0
     83    display:none;
     84    height:0
    8585}
    8686
    8787.hide,[hidden],template {
    88         display:none
     88    display:none
    8989}
    9090
    9191a {
    92         background-color:transparent
     92    background-color:transparent
    9393}
    9494
    9595a:active,a:hover {
    96         outline:0
     96    outline:0
    9797}
    9898
    9999abbr[title] {
    100         border-bottom:1px dotted
     100    border-bottom:1px dotted
    101101}
    102102
    103103b,optgroup,strong {
    104         font-weight:700
     104    font-weight:700
    105105}
    106106
    107107dfn {
    108         font-style:italic
     108    font-style:italic
    109109}
    110110
    111111h1 {
    112         font-size:2em;
    113         margin:.67em 0
     112    font-size:2em;
     113    margin:.67em 0
    114114}
    115115
    116116mark {
    117         background:#ff0;
    118         color:#000
     117    background:#ff0;
     118    color:#000
    119119}
    120120
    121121small {
    122         font-size:80%
     122    font-size:80%
    123123}
    124124
    125125sub,sup {
    126         font-size:75%;
    127         line-height:0;
    128         vertical-align:baseline
     126    font-size:75%;
     127    line-height:0;
     128    vertical-align:baseline
    129129}
    130130
    131131sup {
    132         top:-.5em
     132    top:-.5em
    133133}
    134134
    135135sub {
    136         bottom:-.25em
     136    bottom:-.25em
    137137}
    138138
    139139svg:not(:root) {
    140         overflow:hidden
     140    overflow:hidden
    141141}
    142142
    143143figure {
    144         margin:1em 40px
     144    margin:1em 40px
    145145}
    146146
    147147hr {
    148         box-sizing:content-box;
    149         height:0
     148    box-sizing:content-box;
     149    height:0
    150150}
    151151
    152152pre,textarea {
    153         overflow:auto
     153    overflow:auto
    154154}
    155155
    156156code,kbd,pre,samp {
    157         font-family:monospace,monospace;
    158         font-size:1em
     157    font-family:monospace,monospace;
     158    font-size:1em
    159159}
    160160
    161161button,input,optgroup,select,textarea {
    162         color:inherit;
    163         font:inherit;
    164         margin:0
     162    color:inherit;
     163    font:inherit;
     164    margin:0
    165165}
    166166
    167167button {
    168         overflow:visible
     168    overflow:visible
    169169}
    170170
    171171button,select {
    172         text-transform:none
     172    text-transform:none
    173173}
    174174
    175175button,html input[type=button],input[type=reset],input[type=submit] {
    176         -webkit-appearance:button;
    177         cursor:pointer
     176    -webkit-appearance:button;
     177    cursor:pointer
    178178}
    179179
    180180button[disabled],html input[disabled] {
    181         cursor:default
     181    cursor:default
    182182}
    183183
    184184button::-moz-focus-inner,input::-moz-focus-inner {
    185         border:0;
    186         padding:0
     185    border:0;
     186    padding:0
    187187}
    188188
    189189input {
    190         line-height:normal
     190    line-height:normal
    191191}
    192192
    193193input[type=checkbox],input[type=radio] {
    194         box-sizing:border-box;
    195         padding:0
     194    box-sizing:border-box;
     195    padding:0
    196196}
    197197
    198198input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button {
    199         height:auto
     199    height:auto
    200200}
    201201
    202202input[type=search] {
    203         -webkit-appearance:textfield;
    204         box-sizing:content-box
     203    -webkit-appearance:textfield;
     204    box-sizing:content-box
    205205}
    206206
    207207input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration {
    208         -webkit-appearance:none
     208    -webkit-appearance:none
    209209}
    210210
    211211fieldset {
    212         border:1px solid silver;
    213         margin:0 2px;
    214         padding:.35em .625em .75em
     212    border:1px solid silver;
     213    margin:0 2px;
     214    padding:.35em .625em .75em
    215215}
    216216
    217217table {
    218         border-collapse:collapse;
    219         border-spacing:0
     218    border-collapse:collapse;
     219    border-spacing:0
    220220}
    221221
    222222body {
    223         margin:0;
    224         font-family:'Open Sans',sans-serif
     223    margin:0;
     224    font-family:'Open Sans',sans-serif
    225225}
    226226
    227227*,:after,:before {
    228         box-sizing:inherit
     228    box-sizing:inherit
    229229}
    230230
    231231.button {
    232         box-sizing:border-box;
    233         display:inline-block;
    234         text-align:center;
    235         vertical-align:middle;
    236         cursor:pointer;
    237         border:1px solid transparent;
    238         font-style:normal;
    239         white-space:nowrap;
    240         text-overflow:ellipsis;
    241         padding:.3em 1em;
    242         margin:0;
    243         overflow:hidden;
    244         background:#e1e1e1;
    245         color:#151515;
    246         font-size:1em;
    247         line-height:1.25;
    248         max-width:100%
     232    box-sizing:border-box;
     233    display:inline-block;
     234    text-align:center;
     235    vertical-align:middle;
     236    cursor:pointer;
     237    border:1px solid transparent;
     238    font-style:normal;
     239    white-space:nowrap;
     240    text-overflow:ellipsis;
     241    padding:.3em 1em;
     242    margin:0;
     243    overflow:hidden;
     244    background:#e1e1e1;
     245    color:#151515;
     246    font-size:1em;
     247    line-height:1.25;
     248    max-width:100%
    249249}
    250250
    251251.login-content {
    252         background-image:url(../media/img/back-01.jpg);
    253         background-size:cover;
    254         display:-webkit-flex;
    255         display:-ms-flexbox;
    256         display:flex;
    257         -webkit-justify-content:center;
    258         -ms-flex-pack:center;
    259         justify-content:center;
    260         -webkit-align-items:center;
    261         -ms-flex-align:center;
    262         align-items:center;
    263         min-height:300px;
    264         padding:1em 0;
    265         transition:all 250ms ease-in-out
     252    background-image:url(../media/img/back-01.jpg);
     253    background-size:cover;
     254    display:-webkit-flex;
     255    display:-ms-flexbox;
     256    display:flex;
     257    -webkit-justify-content:center;
     258    -ms-flex-pack:center;
     259    justify-content:center;
     260    -webkit-align-items:center;
     261    -ms-flex-align:center;
     262    align-items:center;
     263    min-height:300px;
     264    padding:1em 0;
     265    transition:all 250ms ease-in-out
    266266}
    267267
    268268@media (min-width:1024px) {
    269         .login-content {
     269    .login-content {
    270270        min-height:550px
    271 }
     271    }
    272272
    273273
     
    275275
    276276@media (min-width:1440px) {
    277         .login-content {
     277    .login-content {
    278278        min-height:650px
    279 }
     279    }
    280280
    281281
     
    283283
    284284.login-content .message {
    285         padding:.5em;
    286         margin:0;
    287         text-align:center;
    288         font-size:13px;
    289         font-size:.8125rem
     285    padding:.5em;
     286    margin:0;
     287    text-align:center;
     288    font-size:13px;
     289    font-size:.8125rem
    290290}
    291291
    292292.login-content .message.message-error {
    293         color:#fff;
    294         background-color:red
     293    color:#fff;
     294    background-color:red
    295295}
    296296
    297297.login-content form {
    298         width:100%;
    299         max-width:350px
     298    width:100%;
     299    max-width:350px
    300300}
    301301
    302302.login-content legend {
    303         display:none
     303    display:none
    304304}
    305305
    306306.login-content fieldset {
    307         border:none;
    308         background-color:rgba(21,21,21,.6);
    309         border-radius:6px;
    310         padding:2em
     307    border:none;
     308    background-color:rgba(21,21,21,.6);
     309    border-radius:6px;
     310    padding:2em
    311311}
    312312
    313313.login-content fieldset>div {
    314         margin-bottom:1.5em
     314    margin-bottom:1.5em
    315315}
    316316
    317317.login-content input {
    318         width:100%;
    319         padding:.5em .75em;
    320         border:1px solid transparent;
    321         border-radius:2px
     318    width:100%;
     319    padding:.5em .75em;
     320    border:1px solid transparent;
     321    border-radius:2px
    322322}
    323323
    324324.login-content input:focus {
    325         border-color:#EE4056
     325    border-color:#EE4056
    326326}
    327327
    328328.login-content label {
    329         display:block;
    330         font-size:14px;
    331         font-size:.875rem;
    332         color:#959595;
    333         margin-bottom:.25em
     329    display:block;
     330    font-size:14px;
     331    font-size:.875rem;
     332    color:#959595;
     333    margin-bottom:.25em
    334334}
    335335
    336336.login-content .button {
    337         transition:all .5s ease-in-out;
    338         background-color:#EE4056;
    339         color:#fff;
    340         margin-bottom:1em;
    341         text-transform:uppercase
     337    transition:all .5s ease-in-out;
     338    background-color:#EE4056;
     339    color:#fff;
     340    margin-bottom:1em;
     341    text-transform:uppercase
    342342}
    343343
    344344.login-content .button:hover {
    345         background-color:#e6152f
     345    background-color:#e6152f
    346346}
    347347
    348348.signup-content {
    349         background-image:url(../media/img/signup-01.jpg);
    350         background-size:cover;
    351         display:-webkit-flex;
    352         display:-ms-flexbox;
    353         display:flex;
    354         -webkit-justify-content:center;
    355         -ms-flex-pack:center;
    356         justify-content:center;
    357         -webkit-align-items:center;
    358         -ms-flex-align:center;
    359         align-items:center;
    360         min-height:300px;
    361         padding:1em 0;
    362         transition:all 250ms ease-in-out
     349    background-image:url(../media/img/signup-01.jpg);
     350    background-size:cover;
     351    display:-webkit-flex;
     352    display:-ms-flexbox;
     353    display:flex;
     354    -webkit-justify-content:center;
     355    -ms-flex-pack:center;
     356    justify-content:center;
     357    -webkit-align-items:center;
     358    -ms-flex-align:center;
     359    align-items:center;
     360    min-height:300px;
     361    padding:1em 0;
     362    transition:all 250ms ease-in-out
    363363}
    364364
    365365@media (min-width:1024px) {
    366         .signup-content {
     366    .signup-content {
    367367        min-height:550px
    368 }
     368    }
    369369
    370370
     
    372372
    373373@media (min-width:1440px) {
    374         .signup-content {
     374    .signup-content {
    375375        min-height:650px
    376 }
     376    }
    377377
    378378
     
    380380
    381381.signup-content form {
    382         width:100%;
    383         max-width:40%
     382    width:100%;
     383    max-width:40%
    384384}
    385385
    386386.signup-content ul.error li {
    387         color:#fff;
    388         background-color:red
     387    color:#fff;
     388    background-color:red
    389389}
    390390
    391391.signup-content legend {
    392         display:none
     392    display:none
    393393}
    394394
    395395.signup-content fieldset {
    396         border:none;
    397         background-color:rgba(21,21,21,.6);
    398         border-radius:6px;
    399         padding:2em
     396    border:none;
     397    background-color:rgba(21,21,21,.6);
     398    border-radius:6px;
     399    padding:2em
    400400}
    401401
    402402.signup-content fieldset>div {
    403         margin-bottom:1.5em
     403    margin-bottom:1.5em
    404404}
    405405
    406406.signup-content input {
    407         width:100%;
    408         padding:.5em .75em;
    409         border:1px solid transparent;
    410         border-radius:2px
     407    width:100%;
     408    padding:.5em .75em;
     409    border:1px solid transparent;
     410    border-radius:2px
    411411}
    412412
    413413.signup-content input:focus {
    414         border-color:#EE4056
     414    border-color:#EE4056
    415415}
    416416
    417417.signup-content label {
    418         display:block;
    419         font-size:14px;
    420         font-size:.875rem;
    421         color:#959595;
    422         margin-bottom:.25em
     418    display:block;
     419    font-size:14px;
     420    font-size:.875rem;
     421    color:#959595;
     422    margin-bottom:.25em
    423423}
    424424
    425425.signup-content .button {
    426         transition:all .5s ease-in-out;
    427         background-color:#EE4056;
    428         color:#fff;
    429         margin-bottom:1em;
    430         text-transform:uppercase
     426    transition:all .5s ease-in-out;
     427    background-color:#EE4056;
     428    color:#fff;
     429    margin-bottom:1em;
     430    text-transform:uppercase
    431431}
    432432
    433433.logo span,.workout-title:before {
    434         transition:all 250ms ease-in-out
     434    transition:all 250ms ease-in-out
    435435}
    436436
    437437.logo-open,.logo:hover span {
    438         color:#EE4056
     438    color:#EE4056
    439439}
    440440
    441441.header-content .description,.logo {
    442         margin:0
     442    margin:0
    443443}
    444444
    445445.signup-content .button:hover {
    446         background-color:#e6152f
     446    background-color:#e6152f
    447447}
    448448
    449449.header-content {
    450         padding:1em 1.5em
     450    padding:1em 1.5em
    451451}
    452452
    453453.logo {
    454         font-size:24px;
    455         font-size:1.5rem;
    456         line-height:1em;
    457         font-weight:800;
    458         text-transform:uppercase;
    459         display:inline-block
     454    font-size:24px;
     455    font-size:1.5rem;
     456    line-height:1em;
     457    font-weight:800;
     458    text-transform:uppercase;
     459    display:inline-block
    460460}
    461461
    462462.logo-open {
    463         display:block
     463    display:block
    464464}
    465465
    466466.logo-outs,.logo-work {
    467         color:#f8b5be
     467    color:#f8b5be
    468468}
    469469
    470470.nav-site {
    471         font-size:13px;
    472         font-size:.8125rem
     471    font-size:13px;
     472    font-size:.8125rem
    473473}
    474474
    475475.nav-site ul {
    476         list-style-type:none;
    477         padding:0;
    478         margin:0;
    479         display:-webkit-flex;
    480         display:-ms-flexbox;
    481         display:flex;
    482         -webkit-flex-direction:row;
    483         -ms-flex-direction:row;
    484         flex-direction:row;
    485         -webkit-justify-content:space-between;
    486         -ms-flex-pack:justify;
    487         justify-content:space-between
     476    list-style-type:none;
     477    padding:0;
     478    margin:0;
     479    display:-webkit-flex;
     480    display:-ms-flexbox;
     481    display:flex;
     482    -webkit-flex-direction:row;
     483    -ms-flex-direction:row;
     484    flex-direction:row;
     485    -webkit-justify-content:space-between;
     486    -ms-flex-pack:justify;
     487    justify-content:space-between
    488488}
    489489
    490490.nav-site li {
    491         border-left:1px solid #e1e1e1;
    492         -webkit-flex-grow:1;
    493         -ms-flex-positive:1;
    494         flex-grow:1;
    495         text-align:center
     491    border-left:1px solid #e1e1e1;
     492    -webkit-flex-grow:1;
     493    -ms-flex-positive:1;
     494    flex-grow:1;
     495    text-align:center
    496496}
    497497
    498498.nav-site a {
    499         color:#959595;
    500         padding:1.25em;
    501         display:block
     499    color:#959595;
     500    padding:1.25em;
     501    display:block
    502502}
    503503
    504504.nav-site .is-active a,.nav-site a:hover {
    505         color:#151515
     505    color:#151515
    506506}
    507507
    508508.add-workout>a {
    509         color:#EE4056
     509    color:#EE4056
    510510}
    511511
    512512.add-workout>a:before {
    513         content:"+";
    514         font-weight:800;
    515         font-size:32px;
    516         font-size:2rem;
    517         line-height:0;
    518         position:relative;
    519         top:8px
     513    content:"+";
     514    font-weight:800;
     515    font-size:32px;
     516    font-size:2rem;
     517    line-height:0;
     518    position:relative;
     519    top:8px
    520520}
    521521
    522522.add-workout:hover:after,.bike:before {
    523         content:""
     523    content:""
    524524}
    525525
    526526.add-workout>a span {
    527         display:none
     527    display:none
    528528}
    529529
    530530.add-workout>a:hover {
    531         background-color:#EE4056;
    532         color:#fff
     531    background-color:#EE4056;
     532    color:#fff
    533533}
    534534
    535535.add-workout:hover:after {
    536         position:absolute;
    537         background-color:#fff;
    538         bottom:-1px;
    539         width:100%;
    540         height:1px;
    541         display:block;
    542         z-index:20
     536    position:absolute;
     537    background-color:#fff;
     538    bottom:-1px;
     539    width:100%;
     540    height:1px;
     541    display:block;
     542    z-index:20
    543543}
    544544
    545545.add-workout:hover ul {
    546         display:inline-block
     546    display:inline-block
    547547}
    548548
    549549.add-workout ul {
    550         display:none;
    551         -webkit-flex-direction:column;
    552         -ms-flex-direction:column;
    553         flex-direction:column;
    554         position:absolute;
    555         background-color:rgba(255,255,255,.95);
    556         right:-1px;
    557         border:1px solid #e1e1e1;
    558         width:153px
     550    display:none;
     551    -webkit-flex-direction:column;
     552    -ms-flex-direction:column;
     553    flex-direction:column;
     554    position:absolute;
     555    background-color:rgba(255,255,255,.95);
     556    right:-1px;
     557    border:1px solid #e1e1e1;
     558    width:153px;
     559    z-index:10;
    559560}
    560561
    561562.add-workout ul li {
    562         border-left:transparent;
    563         text-align:left
     563    border-left:transparent;
     564    text-align:left
    564565}
    565566
    566567.add-workout ul a:hover {
    567         background-color:#fbfbfb
     568    background-color:#fbfbfb
    568569}
    569570
    570571.description {
    571         font-size:14px;
    572         font-size:.875rem;
    573         font-weight:300;
    574         letter-spacing:.025em;
    575         color:#959595
     572    font-size:14px;
     573    font-size:.875rem;
     574    font-weight:300;
     575    letter-spacing:.025em;
     576    color:#959595
    576577}
    577578
    578579.is-login .header-content {
    579         border-bottom:1px solid #e1e1e1;
    580         display:-webkit-flex;
    581         display:-ms-flexbox;
    582         display:flex;
    583         padding:0 1.5em;
    584         -webkit-justify-content:space-between;
    585         -ms-flex-pack:justify;
    586         justify-content:space-between;
    587         -webkit-align-items:center;
    588         -ms-flex-align:center;
    589         align-items:center
     580    border-bottom:1px solid #e1e1e1;
     581    display:-webkit-flex;
     582    display:-ms-flexbox;
     583    display:flex;
     584    padding:0 1.5em;
     585    -webkit-justify-content:space-between;
     586    -ms-flex-pack:justify;
     587    justify-content:space-between;
     588    -webkit-align-items:center;
     589    -ms-flex-align:center;
     590    align-items:center
    590591}
    591592
    592593.is-login .description {
    593         display:none
     594    display:none
    594595}
    595596
    596597.is-login .logo-open {
    597         display:inline-block
     598    display:inline-block
    598599}
    599600
    600601.workout-content {
    601         padding:2em 1em
     602    padding:2em 1em
    602603}
    603604
    604605@media (min-width:480px) {
    605         .workout-content {
     606    .workout-content {
    606607        padding:2em 6em
    607 }
     608    }
    608609
    609610
     
    611612
    612613.workout-list {
    613         margin-right:2em
     614    margin-right:2em
    614615}
    615616
    616617.workout-list>h2 {
    617         font-weight:300;
    618         margin:0 0 1.5em
     618    font-weight:300;
     619    margin:0 0 1.5em
    619620}
    620621
    621622.workout-resume {
    622         max-width:540px;
    623         padding:1.5em 0;
    624         position:relative
     623    max-width:540px;
     624    padding:1.5em 0;
     625    position:relative
    625626}
    626627
    627628@media (min-width:800px) {
    628         .workout-content {
     629    .workout-content {
    629630        display:-webkit-flex;
    630631        display:-ms-flexbox;
     
    633634        -ms-flex-pack:justify;
    634635        justify-content:space-between
    635 }
    636 
    637 .workout-resume {
     636    }
     637
     638    .workout-resume {
    638639        padding-left:2em;
    639640        border-left:1px solid #e1e1e1;
    640641        transition:all 250ms ease-in-out
    641 }
     642    }
    642643
    643644
     
    645646
    646647.workout-resume:hover {
    647         border-color:#151515
     648    border-color:#151515
    648649}
    649650
    650651.workout-resume:hover .workout-title:before {
    651         color:#151515
     652    color:#151515
    652653}
    653654
    654655.workout-resume ul {
    655         padding:0;
    656         list-style-type:none;
    657         font-size:13px;
    658         font-size:.8125rem
     656    padding:0;
     657    list-style-type:none;
     658    font-size:13px;
     659    font-size:.8125rem
    659660}
    660661
    661662.workout-resume ul a {
    662         display:block
     663    display:block
    663664}
    664665
    665666.bike:before {
    666         display:block;
    667         width:30px;
    668         height:30px;
    669         background-image:url(../media/img/bike.svg);
    670         background-size:100%
     667    display:block;
     668    width:30px;
     669    height:30px;
     670    background-image:url(../media/img/bike.svg);
     671    background-size:100%
    671672}
    672673
    673674.workout-title {
    674         font-size:14px;
    675         font-size:.875rem;
    676         margin:0;
    677         position:relative
     675    font-size:14px;
     676    font-size:.875rem;
     677    margin:0;
     678    position:relative
    678679}
    679680
    680681.workout-title:before {
    681         content:"▶";
    682         position:absolute;
    683         left:-3.25em;
    684         top:.35em;
    685         color:#e1e1e1;
    686         font-size:10px;
    687         font-size:.625rem
     682    content:"▶";
     683    position:absolute;
     684    left:-3.25em;
     685    top:.35em;
     686    color:#e1e1e1;
     687    font-size:10px;
     688    font-size:.625rem
    688689}
    689690
    690691.workout-title a {
    691         color:#151515
     692    color:#151515
    692693}
    693694
    694695.workout-title a:hover {
    695         color:#EE4056
     696    color:#EE4056
    696697}
    697698
    698699.workout-info {
    699         display:-webkit-flex;
    700         display:-ms-flexbox;
    701         display:flex;
    702         -webkit-align-items:center;
    703         -ms-flex-align:center;
    704         align-items:center;
    705         margin:.25em 0
     700    display:-webkit-flex;
     701    display:-ms-flexbox;
     702    display:flex;
     703    -webkit-align-items:center;
     704    -ms-flex-align:center;
     705    align-items:center;
     706    margin:.25em 0
    706707}
    707708
    708709.workout-info li {
    709         color:#959595
     710    color:#959595
    710711}
    711712
    712713.workout-info li:after {
    713         content:"|";
    714         margin:0 .5em
     714    content:"|";
     715    margin:0 .5em
    715716}
    716717
    717718.workout-info li:last-child:after {
    718         content:"";
    719         margin:0
     719    content:"";
     720    margin:0
    720721}
    721722
    722723.workout-intro {
    723         font-size:13px;
    724         font-size:.8125rem;
    725         color:#151515
     724    font-size:13px;
     725    font-size:.8125rem;
     726    color:#151515
    726727}
    727728
    728729.workout-options {
    729         display:inline-block;
    730         border:1px solid #e1e1e1;
    731         margin-bottom:0;
    732         border-radius:4px
     730    display:inline-block;
     731    border:1px solid #e1e1e1;
     732    margin-bottom:0;
     733    border-radius:4px
    733734}
    734735
    735736.workout-options li {
    736         display:inline-block;
    737         border-right:1px solid #e1e1e1
     737    display:inline-block;
     738    border-right:1px solid #e1e1e1
    738739}
    739740
    740741.workout-options li:last-child {
    741         border-right:none
     742    border-right:none
    742743}
    743744
    744745.workout-options a {
    745         color:#959595;
    746         padding:.5em .75em
     746    color:#959595;
     747    padding:.5em .75em
    747748}
    748749
    749750.workout-options a:hover {
    750         color:#151515
     751    color:#151515
    751752}
    752753
    753754.owo-del a:hover {
    754         color:red
     755    color:red
    755756}
    756757
    757758.workout-aside {
    758         width:100%
    759 }
     759    width:100%
     760}
     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
    760828
    761829@media (min-width:800px) {
    762         .workout-aside {
     830    .workout-aside {
    763831        max-width:300px;
    764832        padding-left:1.5em;
    765833        border-left:1px solid #e1e1e1
    766 }
    767 
    768 
    769 }
    770 
     834    }
     835
     836
     837}
  • ow/static/css/openworkouts.css

    r7388b68 r02048a6  
    22#map {
    33    height: 360px;
     4    z-index: 0;
    45}
    56
  • ow/static/js/ow.js

    r7388b68 r02048a6  
    7373
    7474        var ele_container = elevation.addTo(map);
    75         document.getElementById('ow-analysis').appendChild(
    76             ele_container._container);
     75        /* document.getElementById('ow-analysis').appendChild(
     76            ele_container._container); */
    7777    };
    7878
  • ow/templates/dashboard.pt

    r7388b68 r02048a6  
    2727        <h2 tal:content="context.fullname"></h2>
    2828
    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">
    3048
    3149          <article class="workout-resume">
     
    85103
    86104      <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>
    88137      </aside>
    89138
  • ow/templates/workout.pt

    r7388b68 r02048a6  
    1818    <link rel="stylesheet" tal:condition="context.has_gpx"
    1919          href="${request.static_url('ow:static/components/Leaflet.Elevation/dist/leaflet.elevation-0.0.4.css')}" />
    20 
    21     <!--!
    22     <link rel="stylesheet" tal:condition="context.has_gpx"
    23           href="${request.static_url('ow:static/leaflet-elevation/leaflet.elevation.css')}" />
    24 
    25     <link rel="stylesheet" tal:condition="context.has_gpx"
    26           href="${request.static_url('ow:static/leaflet-openworkouts/leaflet.openworkouts.css')}" />
    27     -->
    28 
    2920  </metal:css>
    3021
     
    149140            tal:condition="context.has_gpx"></script>
    150141
    151     <!--!
    152     <script src="${request.static_url('ow:static/leaflet-elevation/leaflet.elevation.min.js')}"
    153             tal:condition="context.has_gpx"></script>
    154     <script src="${request.static_url('ow:static/leaflet-openworkouts/leaflet.openworkouts.src.js')}"
    155             tal:condition="context.has_gpx"></script>
    156     -->
    157142    <script src="${request.static_url('ow:static/js/ow.js')}"
    158143            tal:condition="context.has_gpx"></script>
     
    164149         zoom: 11,
    165150         gpx_url: '${request.resource_url(context, 'gpx')}',
    166          start_icon: '${request.static_url('ow:static/leaflet-gpx/pin-icon-start.png')}',
    167          end_icon: '${request.static_url('ow:static/leaflet-gpx/pin-icon-end.png')}',
    168          shadow: '${request.static_url('ow:static/leaflet-gpx/pin-shadow.png')}',
     151         start_icon: '${request.static_url('ow:static/components/leaflet-gpx/pin-icon-start.png')}',
     152         end_icon: '${request.static_url('ow:static/components/leaflet-gpx/pin-icon-end.png')}',
     153         shadow: '${request.static_url('ow:static/components/leaflet-gpx/pin-shadow.png')}',
    169154     });
    170155     workout_map.render();
  • ow/tests/models/test_user.py

    r7388b68 r02048a6  
     1from datetime import datetime, timedelta, timezone
     2
    13import pytest
    24from pyramid.security import Allow
     
    6870        assert list(root['john'].workout_ids()) == ['1', '2', '3']
    6971        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

    r7388b68 r02048a6  
    152152        request = dummy_request
    153153        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()]
    155246
    156247    def test_profile(self, dummy_request, john):
  • ow/views/user.py

    r7388b68 r02048a6  
     1from calendar import month_name
     2
    13from pyramid.httpexceptions import HTTPFound
    24from pyramid.view import view_config
     
    99101    name='forgot-password',
    100102    renderer='ow:templates/forgot_password.pt')
    101 def recover_password(context, request):
     103def recover_password(context, request):  # pragma: no cover
    102104    # WIP
    103105    Form(request)
     
    112114    Render a dashboard for the current user
    113115    """
    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    }
    116150
    117151
Note: See TracChangeset for help on using the changeset viewer.