Changeset 02048a6 in OpenWorkouts-current
- Timestamp:
- Jan 16, 2019, 11:52:22 AM (5 years ago)
- 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. - Location:
- ow
- Files:
-
- 9 edited
Legend:
- Unmodified
- Added
- Removed
-
ow/models/user.py
r7388b68 r02048a6 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
r7388b68 r02048a6 1 1 img,legend { 2 2 border:0 3 3 } 4 4 5 5 legend,td,th { 6 6 padding:0 7 7 } 8 8 9 9 .add-workout,.button,sub,sup { 10 10 position:relative 11 11 } 12 12 13 13 .button,.header-content a,.workout-resume ul a,.workout-title a { 14 14 text-decoration:none 15 15 } 16 16 17 17 @font-face { 18 19 20 21 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') 22 22 } 23 23 24 24 @font-face { 25 26 27 28 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') 29 29 } 30 30 31 31 @font-face { 32 33 34 35 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') 36 36 } 37 37 38 38 @font-face { 39 40 41 42 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') 43 43 } 44 44 45 45 @font-face { 46 47 48 49 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') 50 50 } 51 51 52 52 @font-face { 53 54 55 56 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') 57 57 } 58 58 59 59 @font-face { 60 61 62 63 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') 64 64 } 65 65 66 66 html { 67 68 69 70 67 font-family:sans-serif; 68 -ms-text-size-adjust:100%; 69 -webkit-text-size-adjust:100%; 70 box-sizing:border-box 71 71 } 72 72 73 73 article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary { 74 74 display:block 75 75 } 76 76 77 77 audio,canvas,progress,video { 78 79 78 display:inline-block; 79 vertical-align:baseline 80 80 } 81 81 82 82 audio:not([controls]) { 83 84 83 display:none; 84 height:0 85 85 } 86 86 87 87 .hide,[hidden],template { 88 88 display:none 89 89 } 90 90 91 91 a { 92 92 background-color:transparent 93 93 } 94 94 95 95 a:active,a:hover { 96 96 outline:0 97 97 } 98 98 99 99 abbr[title] { 100 100 border-bottom:1px dotted 101 101 } 102 102 103 103 b,optgroup,strong { 104 104 font-weight:700 105 105 } 106 106 107 107 dfn { 108 108 font-style:italic 109 109 } 110 110 111 111 h1 { 112 113 112 font-size:2em; 113 margin:.67em 0 114 114 } 115 115 116 116 mark { 117 118 117 background:#ff0; 118 color:#000 119 119 } 120 120 121 121 small { 122 122 font-size:80% 123 123 } 124 124 125 125 sub,sup { 126 127 128 126 font-size:75%; 127 line-height:0; 128 vertical-align:baseline 129 129 } 130 130 131 131 sup { 132 132 top:-.5em 133 133 } 134 134 135 135 sub { 136 136 bottom:-.25em 137 137 } 138 138 139 139 svg:not(:root) { 140 140 overflow:hidden 141 141 } 142 142 143 143 figure { 144 144 margin:1em 40px 145 145 } 146 146 147 147 hr { 148 149 148 box-sizing:content-box; 149 height:0 150 150 } 151 151 152 152 pre,textarea { 153 153 overflow:auto 154 154 } 155 155 156 156 code,kbd,pre,samp { 157 158 157 font-family:monospace,monospace; 158 font-size:1em 159 159 } 160 160 161 161 button,input,optgroup,select,textarea { 162 163 164 162 color:inherit; 163 font:inherit; 164 margin:0 165 165 } 166 166 167 167 button { 168 168 overflow:visible 169 169 } 170 170 171 171 button,select { 172 172 text-transform:none 173 173 } 174 174 175 175 button,html input[type=button],input[type=reset],input[type=submit] { 176 177 176 -webkit-appearance:button; 177 cursor:pointer 178 178 } 179 179 180 180 button[disabled],html input[disabled] { 181 181 cursor:default 182 182 } 183 183 184 184 button::-moz-focus-inner,input::-moz-focus-inner { 185 186 185 border:0; 186 padding:0 187 187 } 188 188 189 189 input { 190 190 line-height:normal 191 191 } 192 192 193 193 input[type=checkbox],input[type=radio] { 194 195 194 box-sizing:border-box; 195 padding:0 196 196 } 197 197 198 198 input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button { 199 199 height:auto 200 200 } 201 201 202 202 input[type=search] { 203 204 203 -webkit-appearance:textfield; 204 box-sizing:content-box 205 205 } 206 206 207 207 input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration { 208 208 -webkit-appearance:none 209 209 } 210 210 211 211 fieldset { 212 213 214 212 border:1px solid silver; 213 margin:0 2px; 214 padding:.35em .625em .75em 215 215 } 216 216 217 217 table { 218 219 218 border-collapse:collapse; 219 border-spacing:0 220 220 } 221 221 222 222 body { 223 224 223 margin:0; 224 font-family:'Open Sans',sans-serif 225 225 } 226 226 227 227 *,:after,:before { 228 228 box-sizing:inherit 229 229 } 230 230 231 231 .button { 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 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% 249 249 } 250 250 251 251 .login-content { 252 253 254 255 256 257 258 259 260 261 262 263 264 265 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 266 266 } 267 267 268 268 @media (min-width:1024px) { 269 269 .login-content { 270 270 min-height:550px 271 }271 } 272 272 273 273 … … 275 275 276 276 @media (min-width:1440px) { 277 277 .login-content { 278 278 min-height:650px 279 }279 } 280 280 281 281 … … 283 283 284 284 .login-content .message { 285 286 287 288 289 285 padding:.5em; 286 margin:0; 287 text-align:center; 288 font-size:13px; 289 font-size:.8125rem 290 290 } 291 291 292 292 .login-content .message.message-error { 293 294 293 color:#fff; 294 background-color:red 295 295 } 296 296 297 297 .login-content form { 298 299 298 width:100%; 299 max-width:350px 300 300 } 301 301 302 302 .login-content legend { 303 303 display:none 304 304 } 305 305 306 306 .login-content fieldset { 307 308 309 310 307 border:none; 308 background-color:rgba(21,21,21,.6); 309 border-radius:6px; 310 padding:2em 311 311 } 312 312 313 313 .login-content fieldset>div { 314 314 margin-bottom:1.5em 315 315 } 316 316 317 317 .login-content input { 318 319 320 321 318 width:100%; 319 padding:.5em .75em; 320 border:1px solid transparent; 321 border-radius:2px 322 322 } 323 323 324 324 .login-content input:focus { 325 325 border-color:#EE4056 326 326 } 327 327 328 328 .login-content label { 329 330 331 332 333 329 display:block; 330 font-size:14px; 331 font-size:.875rem; 332 color:#959595; 333 margin-bottom:.25em 334 334 } 335 335 336 336 .login-content .button { 337 338 339 340 341 337 transition:all .5s ease-in-out; 338 background-color:#EE4056; 339 color:#fff; 340 margin-bottom:1em; 341 text-transform:uppercase 342 342 } 343 343 344 344 .login-content .button:hover { 345 345 background-color:#e6152f 346 346 } 347 347 348 348 .signup-content { 349 350 351 352 353 354 355 356 357 358 359 360 361 362 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 363 363 } 364 364 365 365 @media (min-width:1024px) { 366 366 .signup-content { 367 367 min-height:550px 368 }368 } 369 369 370 370 … … 372 372 373 373 @media (min-width:1440px) { 374 374 .signup-content { 375 375 min-height:650px 376 }376 } 377 377 378 378 … … 380 380 381 381 .signup-content form { 382 383 382 width:100%; 383 max-width:40% 384 384 } 385 385 386 386 .signup-content ul.error li { 387 388 387 color:#fff; 388 background-color:red 389 389 } 390 390 391 391 .signup-content legend { 392 392 display:none 393 393 } 394 394 395 395 .signup-content fieldset { 396 397 398 399 396 border:none; 397 background-color:rgba(21,21,21,.6); 398 border-radius:6px; 399 padding:2em 400 400 } 401 401 402 402 .signup-content fieldset>div { 403 403 margin-bottom:1.5em 404 404 } 405 405 406 406 .signup-content input { 407 408 409 410 407 width:100%; 408 padding:.5em .75em; 409 border:1px solid transparent; 410 border-radius:2px 411 411 } 412 412 413 413 .signup-content input:focus { 414 414 border-color:#EE4056 415 415 } 416 416 417 417 .signup-content label { 418 419 420 421 422 418 display:block; 419 font-size:14px; 420 font-size:.875rem; 421 color:#959595; 422 margin-bottom:.25em 423 423 } 424 424 425 425 .signup-content .button { 426 427 428 429 430 426 transition:all .5s ease-in-out; 427 background-color:#EE4056; 428 color:#fff; 429 margin-bottom:1em; 430 text-transform:uppercase 431 431 } 432 432 433 433 .logo span,.workout-title:before { 434 434 transition:all 250ms ease-in-out 435 435 } 436 436 437 437 .logo-open,.logo:hover span { 438 438 color:#EE4056 439 439 } 440 440 441 441 .header-content .description,.logo { 442 442 margin:0 443 443 } 444 444 445 445 .signup-content .button:hover { 446 446 background-color:#e6152f 447 447 } 448 448 449 449 .header-content { 450 450 padding:1em 1.5em 451 451 } 452 452 453 453 .logo { 454 455 456 457 458 459 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 460 460 } 461 461 462 462 .logo-open { 463 463 display:block 464 464 } 465 465 466 466 .logo-outs,.logo-work { 467 467 color:#f8b5be 468 468 } 469 469 470 470 .nav-site { 471 472 471 font-size:13px; 472 font-size:.8125rem 473 473 } 474 474 475 475 .nav-site ul { 476 477 478 479 480 481 482 483 484 485 486 487 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 488 488 } 489 489 490 490 .nav-site li { 491 492 493 494 495 491 border-left:1px solid #e1e1e1; 492 -webkit-flex-grow:1; 493 -ms-flex-positive:1; 494 flex-grow:1; 495 text-align:center 496 496 } 497 497 498 498 .nav-site a { 499 500 501 499 color:#959595; 500 padding:1.25em; 501 display:block 502 502 } 503 503 504 504 .nav-site .is-active a,.nav-site a:hover { 505 505 color:#151515 506 506 } 507 507 508 508 .add-workout>a { 509 509 color:#EE4056 510 510 } 511 511 512 512 .add-workout>a:before { 513 514 515 516 517 518 519 513 content:"+"; 514 font-weight:800; 515 font-size:32px; 516 font-size:2rem; 517 line-height:0; 518 position:relative; 519 top:8px 520 520 } 521 521 522 522 .add-workout:hover:after,.bike:before { 523 523 content:"" 524 524 } 525 525 526 526 .add-workout>a span { 527 527 display:none 528 528 } 529 529 530 530 .add-workout>a:hover { 531 532 531 background-color:#EE4056; 532 color:#fff 533 533 } 534 534 535 535 .add-workout:hover:after { 536 537 538 539 540 541 542 536 position:absolute; 537 background-color:#fff; 538 bottom:-1px; 539 width:100%; 540 height:1px; 541 display:block; 542 z-index:20 543 543 } 544 544 545 545 .add-workout:hover ul { 546 546 display:inline-block 547 547 } 548 548 549 549 .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; 559 560 } 560 561 561 562 .add-workout ul li { 562 563 563 border-left:transparent; 564 text-align:left 564 565 } 565 566 566 567 .add-workout ul a:hover { 567 568 background-color:#fbfbfb 568 569 } 569 570 570 571 .description { 571 572 573 574 575 572 font-size:14px; 573 font-size:.875rem; 574 font-weight:300; 575 letter-spacing:.025em; 576 color:#959595 576 577 } 577 578 578 579 .is-login .header-content { 579 580 581 582 583 584 585 586 587 588 589 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 590 591 } 591 592 592 593 .is-login .description { 593 594 display:none 594 595 } 595 596 596 597 .is-login .logo-open { 597 598 display:inline-block 598 599 } 599 600 600 601 .workout-content { 601 602 padding:2em 1em 602 603 } 603 604 604 605 @media (min-width:480px) { 605 606 .workout-content { 606 607 padding:2em 6em 607 }608 } 608 609 609 610 … … 611 612 612 613 .workout-list { 613 614 margin-right:2em 614 615 } 615 616 616 617 .workout-list>h2 { 617 618 618 font-weight:300; 619 margin:0 0 1.5em 619 620 } 620 621 621 622 .workout-resume { 622 623 624 623 max-width:540px; 624 padding:1.5em 0; 625 position:relative 625 626 } 626 627 627 628 @media (min-width:800px) { 628 629 .workout-content { 629 630 display:-webkit-flex; 630 631 display:-ms-flexbox; … … 633 634 -ms-flex-pack:justify; 634 635 justify-content:space-between 635 }636 637 .workout-resume {636 } 637 638 .workout-resume { 638 639 padding-left:2em; 639 640 border-left:1px solid #e1e1e1; 640 641 transition:all 250ms ease-in-out 641 }642 } 642 643 643 644 … … 645 646 646 647 .workout-resume:hover { 647 648 border-color:#151515 648 649 } 649 650 650 651 .workout-resume:hover .workout-title:before { 651 652 color:#151515 652 653 } 653 654 654 655 .workout-resume ul { 655 656 657 658 656 padding:0; 657 list-style-type:none; 658 font-size:13px; 659 font-size:.8125rem 659 660 } 660 661 661 662 .workout-resume ul a { 662 663 display:block 663 664 } 664 665 665 666 .bike:before { 666 667 668 669 670 667 display:block; 668 width:30px; 669 height:30px; 670 background-image:url(../media/img/bike.svg); 671 background-size:100% 671 672 } 672 673 673 674 .workout-title { 674 675 676 677 675 font-size:14px; 676 font-size:.875rem; 677 margin:0; 678 position:relative 678 679 } 679 680 680 681 .workout-title:before { 681 682 683 684 685 686 687 682 content:"▶"; 683 position:absolute; 684 left:-3.25em; 685 top:.35em; 686 color:#e1e1e1; 687 font-size:10px; 688 font-size:.625rem 688 689 } 689 690 690 691 .workout-title a { 691 692 color:#151515 692 693 } 693 694 694 695 .workout-title a:hover { 695 696 color:#EE4056 696 697 } 697 698 698 699 .workout-info { 699 700 701 702 703 704 705 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 706 707 } 707 708 708 709 .workout-info li { 709 710 color:#959595 710 711 } 711 712 712 713 .workout-info li:after { 713 714 714 content:"|"; 715 margin:0 .5em 715 716 } 716 717 717 718 .workout-info li:last-child:after { 718 719 719 content:""; 720 margin:0 720 721 } 721 722 722 723 .workout-intro { 723 724 725 724 font-size:13px; 725 font-size:.8125rem; 726 color:#151515 726 727 } 727 728 728 729 .workout-options { 729 730 731 732 730 display:inline-block; 731 border:1px solid #e1e1e1; 732 margin-bottom:0; 733 border-radius:4px 733 734 } 734 735 735 736 .workout-options li { 736 737 737 display:inline-block; 738 border-right:1px solid #e1e1e1 738 739 } 739 740 740 741 .workout-options li:last-child { 741 742 border-right:none 742 743 } 743 744 744 745 .workout-options a { 745 746 746 color:#959595; 747 padding:.5em .75em 747 748 } 748 749 749 750 .workout-options a:hover { 750 751 color:#151515 751 752 } 752 753 753 754 .owo-del a:hover { 754 755 color:red 755 756 } 756 757 757 758 .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 760 828 761 829 @media (min-width:800px) { 762 830 .workout-aside { 763 831 max-width:300px; 764 832 padding-left:1.5em; 765 833 border-left:1px solid #e1e1e1 766 } 767 768 769 } 770 834 } 835 836 837 } -
ow/static/css/openworkouts.css
r7388b68 r02048a6 2 2 #map { 3 3 height: 360px; 4 z-index: 0; 4 5 } 5 6 -
ow/static/js/ow.js
r7388b68 r02048a6 73 73 74 74 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); */ 77 77 }; 78 78 -
ow/templates/dashboard.pt
r7388b68 r02048a6 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/templates/workout.pt
r7388b68 r02048a6 18 18 <link rel="stylesheet" tal:condition="context.has_gpx" 19 19 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 29 20 </metal:css> 30 21 … … 149 140 tal:condition="context.has_gpx"></script> 150 141 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 -->157 142 <script src="${request.static_url('ow:static/js/ow.js')}" 158 143 tal:condition="context.has_gpx"></script> … … 164 149 zoom: 11, 165 150 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')}', 169 154 }); 170 155 workout_map.render(); -
ow/tests/models/test_user.py
r7388b68 r02048a6 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
r7388b68 r02048a6 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
r7388b68 r02048a6 1 from calendar import month_name 2 1 3 from pyramid.httpexceptions import HTTPFound 2 4 from pyramid.view import view_config … … 99 101 name='forgot-password', 100 102 renderer='ow:templates/forgot_password.pt') 101 def recover_password(context, request): 103 def recover_password(context, request): # pragma: no cover 102 104 # WIP 103 105 Form(request) … … 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.