From 26a1532f6a7e861edf048b10787ed4e81773ad21 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Wed, 11 Feb 2015 09:05:58 +0100 Subject: [PATCH 01/17] Start working on a calendar day view See #103 --- wger/core/api/serializers.py | 8 +- wger/core/api/views.py | 12 ++ wger/core/static/js/workout-manager.js | 38 +++++- wger/manager/templates/workout/calendar.html | 5 +- .../templates/workout/calendar_day.html | 120 ++++++++++++++++++ wger/manager/templates/workout/timer.html | 3 + wger/manager/urls.py | 5 +- wger/manager/views/log.py | 24 +++- 8 files changed, 204 insertions(+), 11 deletions(-) create mode 100644 wger/manager/templates/workout/calendar_day.html diff --git a/wger/core/api/serializers.py b/wger/core/api/serializers.py index 0d679b73e..b2a330ac2 100644 --- a/wger/core/api/serializers.py +++ b/wger/core/api/serializers.py @@ -26,4 +26,10 @@ class UserprofileSerializer(serializers.ModelSerializer): ''' class Meta: model = UserProfile - exclude = ('user',) + + +class UsernameSerializer(serializers.Serializer): + ''' + Serializer to extract the username + ''' + username = serializers.CharField() diff --git a/wger/core/api/views.py b/wger/core/api/views.py index 85af2bcf0..0c7bac669 100644 --- a/wger/core/api/views.py +++ b/wger/core/api/views.py @@ -17,11 +17,14 @@ from django.contrib.auth.models import User from rest_framework import viewsets +from rest_framework.response import Response +from rest_framework.decorators import link from wger.core.models import UserProfile from wger.core.models import Language from wger.core.models import DaysOfWeek from wger.core.models import License +from wger.core.api.serializers import UsernameSerializer from wger.core.api.serializers import UserprofileSerializer from wger.utils.permissions import UpdateOnlyPermission from wger.utils.permissions import WgerPermission @@ -49,6 +52,15 @@ class UserProfileViewSet(viewsets.ModelViewSet): ''' return [(User, 'user')] + @link() + def username(self, request, pk): + ''' + Return the username + ''' + + user = self.get_object().user + return Response(UsernameSerializer(user).data) + class LanguageViewSet(viewsets.ReadOnlyModelViewSet): ''' diff --git a/wger/core/static/js/workout-manager.js b/wger/core/static/js/workout-manager.js index 1d0e3c728..a105bff85 100644 --- a/wger/core/static/js/workout-manager.js +++ b/wger/core/static/js/workout-manager.js @@ -165,6 +165,30 @@ function set_profile_field(field, newValue) { }); } + +function get_username() { + /* + * Get the current user's username + * + * Do not use with anonymous users! + * + * Syncronous request, use sparingly! + */ + + var user_id = get_profile_field('user'); + var result; + $.ajax({ + url:'/api/v2/userprofile/' + user_id + '/username/', + type: 'GET', + async: false, + success: function(user) { + result = user.username; + } + }); + return result; +} + + function get_profile_field(field) { /* * Get a single field from the user's profile @@ -174,13 +198,13 @@ function get_profile_field(field) { var result; $.ajax({ - url:'/api/v2/userprofile/', - type: 'GET', - async: false, - success: function(userprofile) { - result = userprofile.results[0][field]; - } - }); + url:'/api/v2/userprofile/', + type: 'GET', + async: false, + success: function(userprofile) { + result = userprofile.results[0][field]; + } + }); return result; } diff --git a/wger/manager/templates/workout/calendar.html b/wger/manager/templates/workout/calendar.html index 619b1e0a6..f40faf220 100644 --- a/wger/manager/templates/workout/calendar.html +++ b/wger/manager/templates/workout/calendar.html @@ -50,7 +50,10 @@ $(document).ready(function() { {% for date in logs %}
-

{{date}}

+

+ {{date}} + {% trans 'Detail page' %} +

{% regroup logs|get_item:date by exercise as grouped_logs %} diff --git a/wger/manager/templates/workout/calendar_day.html b/wger/manager/templates/workout/calendar_day.html new file mode 100644 index 000000000..5e120959d --- /dev/null +++ b/wger/manager/templates/workout/calendar_day.html @@ -0,0 +1,120 @@ +{% extends "base.html" %} +{% load i18n staticfiles wger_extras %} + +{# #} +{# Opengraph #} +{# #} +{% block opengraph %} + {{ block.super }} + + + {% with username=owner_user.username %} + + {% endwith %} +{% endblock %} + +{# #} +{# Title #} +{# #} +{% block title %}{% trans "Workout log" %} – {{date}}{% endblock %} + + +{# #} +{# Content #} +{# #} +{% block content %} +{% if logs %} + {% with first_log=logs|first %} + {% with session=first_log.get_workout_session %} +
+ + + + + {% if is_owner %} +
+ {% if session %} + {% trans "Edit workout session" %} + {% else %} + {% trans "Add workout impression" %} + {% endif %} +
+ {% endif %} +
+ {% if session %} + + + + + + + + + + + + + +
{% trans "Time" %} + {% if session.time_start %} + {{session.time_start|time:"H:i"}} - {{session.time_end|time:"H:i"}} + {% else %} + -/- + {% endif %} +
{% trans "General impression" %}{% trans session.get_impression_display %}
{% trans "Notes" %}{{session.notes|default:'-/-'}}
+ {% endif %} + {% endwith %} + {% endwith %} + + + + {% regroup logs by exercise as grouped_logs %} + {% for i in grouped_logs %} + + + + + + {% for log in i.list %} + + + + {% endfor %} +
{{ i.grouper }}
+ {{log.reps}} × {{log.weight}} {% trans_weight_unit 'kg' owner_user %} + + {% if is_owner %} + + + {% trans 'Delete' %} + + {% trans 'Edit' %} + + {% endif %} +
+ {% endfor %} +{% else %} + {% trans "Nothing found" %} +{% endif %} +{% endblock %} + + +{# #} +{# Sidebar #} +{# #} +{% block sidebar %} +{% endblock %} diff --git a/wger/manager/templates/workout/timer.html b/wger/manager/templates/workout/timer.html index 5275bfbc5..1802c463c 100644 --- a/wger/manager/templates/workout/timer.html +++ b/wger/manager/templates/workout/timer.html @@ -239,6 +239,9 @@ $(document).ready(function() { // After clicking on the save button once, disable it function( data ) { + var today = new Date(); + var date = '/' + today.getFullYear() + '/' + today.getMonth() + '/' + today.getDay(); + window.location = '/' + get_current_language() + '/workout/calendar/' + get_username() + date; $('#save_button_form').unbind("click"); $('#save_button_form').addClass('disabled'); $('#save_button_form').bind("click", function(e) { diff --git a/wger/manager/urls.py b/wger/manager/urls.py index 5298343c1..d57413446 100644 --- a/wger/manager/urls.py +++ b/wger/manager/urls.py @@ -77,6 +77,9 @@ patterns_workout = patterns('', url(r'^calendar/(?P\d{4})/(?P\d{1,2})$', log.calendar, name='calendar'), + url(r'^calendar/(?P[\w.@+-]+)/(?P\d{4})/(?P\d{1,2})/(?P\d{1,2})$', + log.day, + name='calendar-day'), url(r'^(?P\d+)/ical/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})$', ical.export, name='ical'), @@ -208,4 +211,4 @@ urlpatterns = patterns('', url(r'^session/', include(patterns_session, namespace="session")), url(r'^schedule/', include(patterns_schedule, namespace="schedule")), url(r'^schedule/step/', include(patterns_step, namespace="step")), -) \ No newline at end of file +) diff --git a/wger/manager/views/log.py b/wger/manager/views/log.py index 90a628ae4..29d57335e 100644 --- a/wger/manager/views/log.py +++ b/wger/manager/views/log.py @@ -385,7 +385,6 @@ def calendar(request, username=None, year=None, month=None): ''' is_owner, user = check_access(request.user, username) - logger.info('aa bb cc') uid, token = make_token(user) year = int(year) if year else datetime.date.today().year month = int(month) if month else datetime.date.today().month @@ -425,3 +424,26 @@ def calendar(request, username=None, year=None, month=None): context['month_list'] = WorkoutLog.objects.filter(user=user).dates('date', 'month') context['show_shariff'] = is_owner and user.userprofile.ro_access return render(request, 'workout/calendar.html', context) + + +def day(request, username, year, month, day): + ''' + Show the logs for a single day + ''' + is_owner, user = check_access(request.user, username) + + try: + date = datetime.date(int(year), int(month), int(day)) + except ValueError as e: + logger.error("Error on date: {0}".format(e)) + return HttpResponseForbidden() + logs = WorkoutLog.objects.filter(user=user, + date=date).order_by('id', 'exercise') + context = {} + context['logs'] = logs + context['date'] = date + context['owner_user'] = user + context['is_owner'] = is_owner + context['show_shariff'] = is_owner and user.userprofile.ro_access + + return render(request, 'workout/calendar_day.html', context) From 213f35be1fee16b15131134cd19bdba41f51820e Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Wed, 11 Feb 2015 12:59:14 +0100 Subject: [PATCH 02/17] Correctly check results for RelatedMany2ManyManager --- wger/manager/tests/testcase.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wger/manager/tests/testcase.py b/wger/manager/tests/testcase.py index 6f18c91a0..d21802568 100644 --- a/wger/manager/tests/testcase.py +++ b/wger/manager/tests/testcase.py @@ -193,8 +193,8 @@ class WorkoutManagerTestCase(BaseTestCase, TestCase): # TODO: use FOURPLACES when routine branch is merged self.assertEqual(field.quantize(TWOPLACES), decimal.Decimal(value).quantize(TWOPLACES)) - # Related manager, iterate - elif current_field_class == 'ManyRelatedManager': + # Related manager and SortedManyToMany, iterate + elif current_field_class in ('ManyRelatedManager', 'SortedRelatedManager'): for j in field.all(): self.assertIn(j.id, value) From fdcbad67ec919c7669f0061441fd40af5b94874c Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Mon, 16 Feb 2015 09:08:22 +0100 Subject: [PATCH 03/17] Move calendar templates to own folder --- .../{workout/calendar_day.html => calendar/day.html} | 0 .../templates/{workout/calendar.html => calendar/month.html} | 0 .../mobile/{workout/calendar.html => calendar/month.html} | 0 wger/manager/views/log.py | 4 ++-- 4 files changed, 2 insertions(+), 2 deletions(-) rename wger/manager/templates/{workout/calendar_day.html => calendar/day.html} (100%) rename wger/manager/templates/{workout/calendar.html => calendar/month.html} (100%) rename wger/manager/templates/mobile/{workout/calendar.html => calendar/month.html} (100%) diff --git a/wger/manager/templates/workout/calendar_day.html b/wger/manager/templates/calendar/day.html similarity index 100% rename from wger/manager/templates/workout/calendar_day.html rename to wger/manager/templates/calendar/day.html diff --git a/wger/manager/templates/workout/calendar.html b/wger/manager/templates/calendar/month.html similarity index 100% rename from wger/manager/templates/workout/calendar.html rename to wger/manager/templates/calendar/month.html diff --git a/wger/manager/templates/mobile/workout/calendar.html b/wger/manager/templates/mobile/calendar/month.html similarity index 100% rename from wger/manager/templates/mobile/workout/calendar.html rename to wger/manager/templates/mobile/calendar/month.html diff --git a/wger/manager/views/log.py b/wger/manager/views/log.py index 29d57335e..34bee8c31 100644 --- a/wger/manager/views/log.py +++ b/wger/manager/views/log.py @@ -423,7 +423,7 @@ def calendar(request, username=None, year=None, month=None): context['impressions'] = WorkoutSession.IMPRESSION context['month_list'] = WorkoutLog.objects.filter(user=user).dates('date', 'month') context['show_shariff'] = is_owner and user.userprofile.ro_access - return render(request, 'workout/calendar.html', context) + return render(request, 'calendar/month.html', context) def day(request, username, year, month, day): @@ -446,4 +446,4 @@ def day(request, username, year, month, day): context['is_owner'] = is_owner context['show_shariff'] = is_owner and user.userprofile.ro_access - return render(request, 'workout/calendar_day.html', context) + return render(request, 'calendar/day.html', context) From 9331f669e87ceba6c033cfd7d3c17a8813701b3d Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Mon, 16 Feb 2015 18:02:44 +0100 Subject: [PATCH 04/17] Add breadcrumb navigation to calendar day view --- wger/manager/templates/calendar/day.html | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/wger/manager/templates/calendar/day.html b/wger/manager/templates/calendar/day.html index 5e120959d..20f2e48cd 100644 --- a/wger/manager/templates/calendar/day.html +++ b/wger/manager/templates/calendar/day.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% load i18n staticfiles wger_extras %} +{% load i18n staticfiles wger_extras django_bootstrap_breadcrumbs %} {# #} {# Opengraph #} @@ -13,6 +13,18 @@ {% endwith %} {% endblock %} + +{# #} +{# Breadcrumbs #} +{# #} +{% block breadcrumbs %} + {{ block.super }} + + {% breadcrumb_raw date|date:'F' "manager:workout:calendar" owner_user date.year date.month %} + {% breadcrumb_raw date.day "manager:workout:calendar-day" owner_user date.year date.month date.day %} +{% endblock %} + + {# #} {# Title #} {# #} From b027df603472f59bd65fee7bd8e0019775c70291 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Tue, 17 Feb 2015 14:10:22 +0100 Subject: [PATCH 05/17] Refactor calendar template code The output of process_log_entries was not suited for calendar templates, this was replaced by a new helper, group_log_entries. Also some common template code was moved to a commonly used partial template. --- wger/manager/templates/calendar/day.html | 103 +++++------------- wger/manager/templates/calendar/month.html | 66 ++--------- .../calendar/partial_overview_table.html | 43 ++++++++ .../templates/mobile/calendar/month.html | 78 ++++--------- wger/manager/views/log.py | 7 +- wger/weight/helpers.py | 24 ++++ 6 files changed, 128 insertions(+), 193 deletions(-) create mode 100644 wger/manager/templates/calendar/partial_overview_table.html diff --git a/wger/manager/templates/calendar/day.html b/wger/manager/templates/calendar/day.html index 20f2e48cd..436fac99f 100644 --- a/wger/manager/templates/calendar/day.html +++ b/wger/manager/templates/calendar/day.html @@ -36,88 +36,45 @@ {# #} {% block content %} {% if logs %} - {% with first_log=logs|first %} - {% with session=first_log.get_workout_session %} -
- + {% for date, value in logs.items %} - + {% include 'calendar/partial_overview_table.html' %} - {% if is_owner %} -
- {% if session %} - {% trans "Edit workout session" %} - {% else %} - {% trans "Add workout impression" %} - {% endif %} -
- {% endif %} -
- {% if session %} - + + {% for exercise, logs in value.logs.items %} +
+ + + + {% for log in logs %} - - - - - - - - - -
{{ exercise }}
{% trans "Time" %} - {% if session.time_start %} - {{session.time_start|time:"H:i"}} - {{session.time_end|time:"H:i"}} - {% else %} - -/- + {{log.reps}} × {{log.weight}} {% trans_weight_unit 'kg' owner_user %} + + {% if is_owner %} + + + {% trans 'Delete' %} + + {% trans 'Edit' %} + {% endif %}
{% trans "General impression" %}{% trans session.get_impression_display %}
{% trans "Notes" %}{{session.notes|default:'-/-'}}
- {% endif %} - {% endwith %} - {% endwith %} + {% endfor %} + - - - {% regroup logs by exercise as grouped_logs %} - {% for i in grouped_logs %} - - - - - - {% for log in i.list %} - - - {% endfor %} -
{{ i.grouper }}
- {{log.reps}} × {{log.weight}} {% trans_weight_unit 'kg' owner_user %} - - {% if is_owner %} - - - {% trans 'Delete' %} - - {% trans 'Edit' %} - - {% endif %} -
{% endfor %} {% else %} {% trans "Nothing found" %} diff --git a/wger/manager/templates/calendar/month.html b/wger/manager/templates/calendar/month.html index f40faf220..8281df529 100644 --- a/wger/manager/templates/calendar/month.html +++ b/wger/manager/templates/calendar/month.html @@ -1,8 +1,5 @@ {% extends "base.html" %} -{% load i18n %} -{% load staticfiles %} -{% load wger_extras %} -{% load cache %} +{% load i18n staticfiles wger_extras cache %} {# #} @@ -48,73 +45,24 @@ $(document).ready(function() { {% cache cache_timeout workout-log-full is_owner owner_user.id current_year current_month %} {{calendar|safe}} -{% for date in logs %} +{% for date, value in logs.items %}
+

{{date}} {% trans 'Detail page' %}

- {% regroup logs|get_item:date by exercise as grouped_logs %} - - {# TODO: what if some of the entries don't belong to a workout? #} - {% with tmp=grouped_logs|first %} - {% with first_log=tmp.list|first %} - {% with session=first_log.get_workout_session %} -
- - - - -
- {% if is_owner %} - {% if session %} - {% trans "Edit workout session" %} - {% else %} - {% trans "Add workout impression" %} - {% endif %} - {% endif %} -
-
- {% if session %} - - - - - - - - - - - - - -
{% trans "Time" %} - {% if session.time_start %} - {{session.time_start|time:"H:i"}} - {{session.time_end|time:"H:i"}} - {% else %} - -/- - {% endif %} -
{% trans "General impression" %}{% trans session.get_impression_display %}
{% trans "Notes" %}{{session.notes|default:'-/-'}}
- {% endif %} - {% endwith %} - {% endwith %} - {% endwith %} - + {% include 'calendar/partial_overview_table.html' %}
- {% for i in grouped_logs %} + {% for exercise, logs in value.logs.items %}
-
{{ i.grouper }}
+
{{ exercise }}
diff --git a/wger/weight/helpers.py b/wger/weight/helpers.py index c58ba648f..10af8efaa 100644 --- a/wger/weight/helpers.py +++ b/wger/weight/helpers.py @@ -27,6 +27,8 @@ from django.core.cache import cache from wger.utils.helpers import DecimalJsonEncoder from wger.utils.cache import cache_mapper from wger.weight.models import WeightEntry +from wger.manager.models import WorkoutSession +from wger.manager.models import WorkoutLog logger = logging.getLogger(__name__) @@ -59,22 +61,42 @@ def parse_weight_csv(request, cleaned_data): return (weight_list, error_list) -def group_log_entries(logs, hash_dict): +def group_log_entries(user, year, month, day=None): ''' Processes and regroups a list of log entries so they can be more easily used in the different calendar pages - :param logs: a list of logs - :param hash_dict: a dictionary in the form (username, year, month) + :param user: the user to filter the logs for + :param year: year + :param month: month + :param day: optional, day + :return: a dictionary with grouped logs by date and exercise ''' + log_hash = hash(frozenset([user.pk, year, month, day])) + + # There can be workout sessions without any associated log entries, so it is + # not enough so simply iterate through the logs + if day: + filter_date = datetime.date(year, month, day) + logs = WorkoutLog.objects.filter(user=user, date=filter_date) + sessions = WorkoutSession.objects.filter(user=user, date=filter_date) + + else: + logs = WorkoutLog.objects.filter(user=user, + date__year=year, + date__month=month) + + sessions = WorkoutSession.objects.filter(user=user, + date__year=year, + date__month=month) - log_hash = hash(hash_dict) out = cache.get(cache_mapper.get_workout_log_list(log_hash)) if not out: - out = {} + + # Logs for entry in logs: if not out.get(entry.date): out[entry.date] = {'date': entry.date, @@ -86,6 +108,15 @@ def group_log_entries(logs, hash_dict): out[entry.date]['logs'][entry.exercise] = [] out[entry.date]['logs'][entry.exercise].append(entry) + + # Sessions + for entry in sessions: + if not out.get(entry.date): + out[entry.date] = {'date': entry.date, + 'workout': entry.workout, + 'session': entry, + 'logs': {}} + cache.set(cache_mapper.get_workout_log_list(log_hash), out) return out From 2019f69179da385c8d436362157234c97fcc9e9f Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Wed, 18 Feb 2015 12:49:40 +0100 Subject: [PATCH 16/17] Don't use a string as dictionary key Instead use a list, the way the Spaghetti Monster intended --- wger/weight/helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wger/weight/helpers.py b/wger/weight/helpers.py index 10af8efaa..bcd972685 100644 --- a/wger/weight/helpers.py +++ b/wger/weight/helpers.py @@ -161,10 +161,10 @@ def process_log_entries(logs): 'id': 'manager:workout:log-%s' % entry.id} # Only unique date, rep and weight combinations - if reps_list.get('{0}-{1}-{2}'.format(entry.date, entry.reps, entry.weight)): + if reps_list.get((entry.date, entry.reps, entry.weight)): continue else: - reps_list['{0}-{1}-{2}'.format(entry.date, entry.reps, entry.weight)] = True + reps_list[(entry.date, entry.reps, entry.weight)] = True # Only add if weight is the maximum for the day if entry.weight != max_weight[entry.date][entry.reps]: From 25a17e8871b2c6c8d4e38ab4e3531fa658662dc9 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Wed, 18 Feb 2015 14:10:35 +0100 Subject: [PATCH 17/17] Don't use frozenset to calculate the cache key --- wger/weight/helpers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wger/weight/helpers.py b/wger/weight/helpers.py index bcd972685..711c4ec3a 100644 --- a/wger/weight/helpers.py +++ b/wger/weight/helpers.py @@ -73,7 +73,10 @@ def group_log_entries(user, year, month, day=None): :return: a dictionary with grouped logs by date and exercise ''' - log_hash = hash(frozenset([user.pk, year, month, day])) + if day: + log_hash = hash((user.pk, year, month, day)) + else: + log_hash = hash((user.pk, year, month)) # There can be workout sessions without any associated log entries, so it is # not enough so simply iterate through the logs