diff --git a/.travis.yml b/.travis.yml
index ae2c27a17..a273b2dd0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -39,7 +39,7 @@ script:
- coverage run --source='.' ./manage.py test
# Formatting
- - pep8 --max-line-length=100 --exclude="urls.py,*migrations*" wger
+ - pep8 wger
# Code coverage
- coverage report
diff --git a/docs/development.rst b/docs/development.rst
index e2cda51d4..6c53aa5e1 100644
--- a/docs/development.rst
+++ b/docs/development.rst
@@ -91,7 +91,7 @@ Contributing
* **Code according to PEP8**: check that the code is structured as per pep8 but
with a maximum line length of 100. This can be checked automatically with the
pep8 tool (pip install pep8) from the command line (travis will do this as part
- of the tests): ``pep8 --max-line-length=100--exclude="urls.py,*migrations*" wger``
+ of the tests): ``pep8 wger``
* **code for python3**: while the application should remain compatible with
python2, use django's suggestion to mantain sanity: code for py3 and treat
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 000000000..719cf062f
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,4 @@
+[pep8]
+exclude = urls.py,*migrations*
+max-line-length = 100
+ignore = W503
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/demo.py b/wger/core/demo.py
index 1dd0786ef..688baa429 100644
--- a/wger/core/demo.py
+++ b/wger/core/demo.py
@@ -116,7 +116,7 @@ def create_demo_entries(user):
exercise = Exercise.objects.get(pk=25)
else:
exercise = Exercise.objects.get(pk=84)
- day_set = Set(exerciseday=day, sets=4, order=2)
+ day_set = Set(exerciseday=day, sets=4, order=2)
day_set.save()
day_set.exercises.add(exercise)
diff --git a/wger/core/management/commands/clear-cache.py b/wger/core/management/commands/clear-cache.py
index 4a2a32185..9720ac8f4 100644
--- a/wger/core/management/commands/clear-cache.py
+++ b/wger/core/management/commands/clear-cache.py
@@ -58,11 +58,30 @@ class Command(BaseCommand):
# Exercises, cached template fragments
if options['clear_template']:
+ if int(options['verbosity']) >= 2:
+ self.stdout.write("*** Clearing templates")
+
for user in User.objects.all():
- for entry in WorkoutLog.objects.dates('date', 'year'):
- for month in range(1, 13):
- # print("User {0}, year {1}, month {2}".format(user.pk, entry.year, month))
- reset_workout_log(user.id, entry.year, month)
+ if int(options['verbosity']) >= 2:
+ self.stdout.write("* Processing user {0}".format(user.username))
+
+ for entry in WorkoutLog.objects.filter(user=user).dates('date', 'year'):
+
+ if int(options['verbosity']) >= 3:
+ self.stdout.write(" Year {0}".format(entry.year))
+ for month in WorkoutLog.objects.filter(user=user,
+ date__year=entry.year).dates('date',
+ 'month'):
+ if int(options['verbosity']) >= 3:
+ self.stdout.write(" Month {0}".format(entry.month))
+ reset_workout_log(user.id, entry.year, entry.month)
+ for day in WorkoutLog.objects.filter(user=user,
+ date__year=entry.year,
+ date__month=month.month).dates('date',
+ 'day'):
+ if int(options['verbosity']) >= 3:
+ self.stdout.write(" Day {0}".format(day.day))
+ reset_workout_log(user.id, entry.year, entry.month, day)
for language in Language.objects.all():
delete_template_fragment_cache('muscle-overview', language.id)
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/core/templatetags/wger_extras.py b/wger/core/templatetags/wger_extras.py
index 898cab06b..0fa3b1c2c 100644
--- a/wger/core/templatetags/wger_extras.py
+++ b/wger/core/templatetags/wger_extras.py
@@ -76,7 +76,7 @@ def pagination(paginator, page):
page_range = paginator.page_range
# Set the template variables
- return {'page': page,
+ return {'page': page,
'page_range': page_range}
diff --git a/wger/core/tests/test_preferences.py b/wger/core/tests/test_preferences.py
index 834a73fa3..59cce80dd 100644
--- a/wger/core/tests/test_preferences.py
+++ b/wger/core/tests/test_preferences.py
@@ -207,8 +207,8 @@ class PreferencesCalculationsTestCase(WorkoutManagerTestCase):
user = User.objects.get(pk=2)
bmi = user.userprofile.calculate_bmi()
self.assertEqual(bmi,
- user.userprofile.weight.quantize(TWOPLACES)
- / decimal.Decimal(1.80 * 1.80).quantize(TWOPLACES))
+ user.userprofile.weight.quantize(TWOPLACES) /
+ decimal.Decimal(1.80 * 1.80).quantize(TWOPLACES))
def test_basal_metabolic_rate(self):
'''
diff --git a/wger/exercises/management/commands/download-exercise-images.py b/wger/exercises/management/commands/download-exercise-images.py
index bf5b6836e..1af1fb143 100644
--- a/wger/exercises/management/commands/download-exercise-images.py
+++ b/wger/exercises/management/commands/download-exercise-images.py
@@ -46,7 +46,7 @@ class Command(BaseCommand):
dest='remote_url',
default='https://wger.de',
help='Remote URL to fetch the exercises from (default: https://wger.de)'),
- )
+ )
help = ('Download exercise images from wger.de and update the local database\n'
'\n'
diff --git a/wger/manager/helpers.py b/wger/manager/helpers.py
index a8836c8b8..cd11c0d15 100644
--- a/wger/manager/helpers.py
+++ b/wger/manager/helpers.py
@@ -15,12 +15,16 @@
# You should have received a copy of the GNU Affero General Public License
# along with Workout Manager. If not, see .
+import datetime
+from calendar import HTMLCalendar
+
from reportlab.lib import colors
from reportlab.lib.units import cm
from reportlab.platypus import Paragraph
from reportlab.platypus import Table
from reportlab.platypus import KeepTogether
+from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from wger.utils.pdf import styleSheet
@@ -148,3 +152,84 @@ def reps_smart_text(settings, set_obj):
setting_list = tmp_reps
return setting_text, setting_list
+
+
+class WorkoutCalendar(HTMLCalendar):
+ '''
+ A calendar renderer, see this blog entry for details:
+ * http://uggedal.com/journal/creating-a-flexible-monthly-calendar-in-django/
+ '''
+ def __init__(self, workout_logs, *args, **kwargs):
+ super(WorkoutCalendar, self).__init__(*args, **kwargs)
+ self.workout_logs = workout_logs
+
+ def formatday(self, day, weekday):
+
+ # days belonging to last or next month are rendered empty
+ if day == 0:
+ return self.day_cell('noday', ' ')
+
+ date_obj = datetime.date(self.year, self.month, day)
+ cssclass = self.cssclasses[weekday]
+ if datetime.date.today() == date_obj:
+ cssclass += ' today'
+
+ # There are no logs for this day, doesn't need special attention
+ if date_obj not in self.workout_logs:
+ return self.day_cell(cssclass, day)
+
+ # Day with a log, set background and link
+ entry = self.workout_logs.get(date_obj)
+
+ # Note: due to circular imports we use can't import the workout session
+ # model to access the impression values directly, so they are hard coded
+ # here.
+ if entry['session']:
+ # Bad
+ if entry['session'].impression == '1':
+ background_css = 'btn-danger'
+ # Good
+ elif entry['session'].impression == '3':
+ background_css = 'btn-success'
+ # Neutral
+ else:
+ background_css = 'btn-warning'
+
+ else:
+ background_css = 'btn-warning'
+
+ url = reverse('manager:log:log', kwargs={'pk': entry['workout'].id})
+ formatted_date = date_obj.strftime('%Y-%m-%d')
+ body = []
+ body.append(''.format(url,
+ formatted_date,
+ background_css))
+ body.append(repr(day))
+ body.append('')
+ return self.day_cell(cssclass, '{0}'.format(''.join(body)))
+
+ def formatmonth(self, year, month):
+ '''
+ Format the table header. This is basically the same code from python's
+ calendar module but with additional bootstrap classes
+ '''
+ self.year, self.month = year, month
+ out = []
+ out.append('
\n')
+ out.append(self.formatmonthname(year, month))
+ out.append('\n')
+ out.append(self.formatweekheader())
+ out.append('\n')
+ for week in self.monthdays2calendar(year, month):
+ out.append(self.formatweek(week))
+ out.append('\n')
+ out.append('
\n')
+ return ''.join(out)
+
+ def day_cell(self, cssclass, body):
+ '''
+ Renders a day cell
+ '''
+ return '{1} | '.format(cssclass, body)
diff --git a/wger/manager/models.py b/wger/manager/models.py
index fdb29df5b..3b3e1be84 100644
--- a/wger/manager/models.py
+++ b/wger/manager/models.py
@@ -660,14 +660,14 @@ class WorkoutLog(models.Model):
'''
Reset cache
'''
- reset_workout_log(self.user_id, self.date.year, self.date.month)
+ reset_workout_log(self.user_id, self.date.year, self.date.month, self.date.day)
super(WorkoutLog, self).save(*args, **kwargs)
def delete(self, *args, **kwargs):
'''
Reset cache
'''
- reset_workout_log(self.user_id, self.date.year, self.date.month)
+ reset_workout_log(self.user_id, self.date.year, self.date.month, self.date.day)
super(WorkoutLog, self).delete(*args, **kwargs)
@@ -677,6 +677,7 @@ class WorkoutSession(models.Model):
Model for a workout session
'''
+ # Note: values hardcoded in manager.helpers.WorkoutCalendar
IMPRESSION_BAD = '1'
IMPRESSION_NEUTRAL = '2'
IMPRESSION_GOOD = '3'
diff --git a/wger/manager/pdf.py b/wger/manager/pdf.py
index d2fc2a63e..256fde8bc 100644
--- a/wger/manager/pdf.py
+++ b/wger/manager/pdf.py
@@ -83,15 +83,15 @@ def workout_log(request, id, uidb64=None, token=None):
{'description': workout},
styleSheet["HeaderBold"])
elements.append(p)
- elements.append(Spacer(10*cm, 0.5*cm))
+ elements.append(Spacer(10 * cm, 0.5 * cm))
# Iterate through the Workout and render the training days
for day in workout.canonical_representation['day_list']:
elements.append(render_workout_day(day, nr_of_weeks=7))
- elements.append(Spacer(10*cm, 0.5*cm))
+ elements.append(Spacer(10 * cm, 0.5 * cm))
# Footer, date and info
- elements.append(Spacer(10*cm, 0.5*cm))
+ elements.append(Spacer(10 * cm, 0.5 * cm))
elements.append(render_footer(request.build_absolute_uri(workout.get_absolute_url())))
# write the document and send the response to the browser
@@ -277,13 +277,13 @@ def workout_view(request, id, uidb64=None, token=None):
elements.append(p)
# Filler
- elements.append(Spacer(10*cm, 0.5*cm))
+ elements.append(Spacer(10 * cm, 0.5 * cm))
# Append the table
elements.append(t)
# Footer, date and info
- elements.append(Spacer(10*cm, 0.5*cm))
+ elements.append(Spacer(10 * cm, 0.5 * cm))
created = datetime.date.today().strftime("%d.%m.%Y")
p = Paragraph('''
%(date)s -
diff --git a/wger/manager/templates/calendar/day.html b/wger/manager/templates/calendar/day.html
new file mode 100644
index 000000000..436fac99f
--- /dev/null
+++ b/wger/manager/templates/calendar/day.html
@@ -0,0 +1,89 @@
+{% extends "base.html" %}
+{% load i18n staticfiles wger_extras django_bootstrap_breadcrumbs %}
+
+{# #}
+{# Opengraph #}
+{# #}
+{% block opengraph %}
+ {{ block.super }}
+
+
+ {% with username=owner_user.username %}
+
+ {% 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 #}
+{# #}
+{% block title %}{% trans "Workout log" %} – {{date}}{% endblock %}
+
+
+{# #}
+{# Content #}
+{# #}
+{% block content %}
+{% if logs %}
+ {% for date, value in logs.items %}
+
+ {% include 'calendar/partial_overview_table.html' %}
+
+
+ {% for exercise, logs in value.logs.items %}
+
+
+ | {{ exercise }} |
+
+ {% for log in logs %}
+
+
+ {{log.reps}} × {{log.weight}} {% trans_weight_unit 'kg' owner_user %}
+
+ {% if is_owner %}
+
+
+
+
+
+
+ {% endif %}
+ |
+
+ {% endfor %}
+
+
+ {% endfor %}
+ {% endfor %}
+{% else %}
+ {% trans "Nothing found" %}
+{% endif %}
+{% endblock %}
+
+
+{# #}
+{# Sidebar #}
+{# #}
+{% block sidebar %}
+{% endblock %}
diff --git a/wger/manager/templates/workout/calendar.html b/wger/manager/templates/calendar/month.html
similarity index 64%
rename from wger/manager/templates/workout/calendar.html
rename to wger/manager/templates/calendar/month.html
index 619b1e0a6..951d4e9a0 100644
--- a/wger/manager/templates/workout/calendar.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 %}
{# #}
@@ -45,73 +42,26 @@ $(document).ready(function() {
{# Content #}
{# #}
{% block content %}
-{% 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}}
- {% 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 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 }}
- {% for log in i.list %}
+ {% for log in logs %}
-
{{log.reps}} × {{log.weight}}
{% if is_owner %}
@@ -142,7 +92,6 @@ $(document).ready(function() {
{% endfor %}
-{% endcache %}
{% endblock %}
@@ -188,9 +137,9 @@ day in your current workout:{% endblocktrans %}
{{current_workout}}
-
{{ impressions.0.1 }}
-
{{ impressions.1.1 }}
-
{{ impressions.2.1 }}
+ {{ impressions.0.1 }}
+ {{ impressions.1.1 }}
+ {{ impressions.2.1 }}
{% trans 'Log' %}
diff --git a/wger/manager/templates/calendar/partial_overview_table.html b/wger/manager/templates/calendar/partial_overview_table.html
new file mode 100644
index 000000000..a67f35e0b
--- /dev/null
+++ b/wger/manager/templates/calendar/partial_overview_table.html
@@ -0,0 +1,43 @@
+{% load i18n %}
+
+
+
+
+
+
+ {% if is_owner %}
+
+ {% endif %}
+
+{% if value.session %}
+
+
+ | {% trans "Time" %} |
+
+ {% if value.session.time_start %}
+ {{value.session.time_start|time:"H:i"}} - {{value.session.time_end|time:"H:i"}}
+ {% else %}
+ -/-
+ {% endif %}
+ |
+
+
+ | {% trans "General impression" %} |
+ {% trans value.session.get_impression_display %} |
+
+
+ | {% trans "Notes" %} |
+ {{value.session.notes|default:'-/-'}} |
+
+
+{% endif %}
\ No newline at end of file
diff --git a/wger/manager/templates/mobile/workout/calendar.html b/wger/manager/templates/mobile/calendar/month.html
similarity index 70%
rename from wger/manager/templates/mobile/workout/calendar.html
rename to wger/manager/templates/mobile/calendar/month.html
index 9cb8e3d47..8e5dcd6c9 100644
--- a/wger/manager/templates/mobile/workout/calendar.html
+++ b/wger/manager/templates/mobile/calendar/month.html
@@ -1,8 +1,5 @@
{% extends "base.html" %}
-{% load i18n %}
-{% load staticfiles %}
-{% load wger_extras %}
-{% load cache %}
+{% load i18n staticfiles wger_extras %}
{# #}
@@ -48,18 +45,15 @@ $(document).ready(function() {
{# Content #}
{# #}
{% block content %}
-{% cache cache_timeout workout-log-mobile is_owner owner_user.id current_year current_month %}
{{calendar|safe}}
-{% for date in logs %}
-{% regroup logs|get_item:date by exercise as grouped_logs %}
-
{# #}
{# Popups to edit or delete the logs #}
{# #}
{% if is_owner %}
-{% for i in grouped_logs %}
-{% for log in i.list %}
+{% for date, value in logs.items %}
+{% for exercise, logs in value.logs.items %}
+{% for log in logs %}