mirror of
https://github.com/wger-project/wger.git
synced 2026-02-18 00:17:51 +01:00
Merge branch 'calendar_day'
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
4
setup.cfg
Normal file
4
setup.cfg
Normal file
@@ -0,0 +1,4 @@
|
||||
[pep8]
|
||||
exclude = urls.py,*migrations*
|
||||
max-line-length = 100
|
||||
ignore = W503
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
'''
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
'''
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -15,12 +15,16 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Workout Manager. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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('<a href="{0}" '
|
||||
'data-log="log-{1}" '
|
||||
'class="btn btn-block {2} calendar-link">'.format(url,
|
||||
formatted_date,
|
||||
background_css))
|
||||
body.append(repr(day))
|
||||
body.append('</a>')
|
||||
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('<table class="month table table-bordered">\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('</table>\n')
|
||||
return ''.join(out)
|
||||
|
||||
def day_cell(self, cssclass, body):
|
||||
'''
|
||||
Renders a day cell
|
||||
'''
|
||||
return '<td class="{0}" style="vertical-align: middle;">{1}</td>'.format(cssclass, body)
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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('''<para align="left">
|
||||
%(date)s -
|
||||
|
||||
89
wger/manager/templates/calendar/day.html
Normal file
89
wger/manager/templates/calendar/day.html
Normal file
@@ -0,0 +1,89 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n staticfiles wger_extras django_bootstrap_breadcrumbs %}
|
||||
|
||||
{# #}
|
||||
{# Opengraph #}
|
||||
{# #}
|
||||
{% block opengraph %}
|
||||
{{ block.super }}
|
||||
<meta property="og:title" content="{% trans 'Workout log' %}">
|
||||
|
||||
{% with username=owner_user.username %}
|
||||
<meta property="og:description" content="{% blocktrans %}Workout log for {{username}} for {{date}}{% endblocktrans %}">
|
||||
{% 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 %}
|
||||
<table class="table table-hover table-bordered" style="margin-top: 2em;">
|
||||
<tr class="active">
|
||||
<th>{{ exercise }}</th>
|
||||
</tr>
|
||||
{% for log in logs %}
|
||||
<tr>
|
||||
<td>
|
||||
{{log.reps}} × {{log.weight}} {% trans_weight_unit 'kg' owner_user %}
|
||||
|
||||
{% if is_owner %}
|
||||
<span class="editoptions">
|
||||
<a href="{% url 'manager:log:delete' log.pk %}"
|
||||
title="{% trans 'Delete' %}"
|
||||
class="wger-modal-dialog">
|
||||
<img src="{% static 'images/icons/trash.svg' %}"
|
||||
width="16"
|
||||
height="16"
|
||||
alt="{% trans 'Delete' %}"></a>
|
||||
<a href="{% url 'manager:log:edit' log.pk %}"
|
||||
title="{% trans 'Edit' %}"
|
||||
class="wger-modal-dialog">
|
||||
<img src="{% static 'images/icons/edit.svg' %}"
|
||||
width="16"
|
||||
height="16"
|
||||
alt="{% trans 'Edit' %}"></a>
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% trans "Nothing found" %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{# #}
|
||||
{# Sidebar #}
|
||||
{# #}
|
||||
{% block sidebar %}
|
||||
{% endblock %}
|
||||
@@ -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 %}
|
||||
<div id="log-{{date|date:'Y-m-d'}}" class="calendar-log-data">
|
||||
<h3>{{date}}</h3>
|
||||
|
||||
{% 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 %}
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<a href="{{first_log.workout.get_absolute_url}}" class="btn btn-default btn-block">{{ first_log.workout }}</a>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<a href="{% url 'manager:log:log' first_log.workout.id %}" class="btn btn-default btn-block">{% trans "Log overview" %}</a>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4">
|
||||
{% if is_owner %}
|
||||
{% if session %}
|
||||
<a href="{% url 'manager:session:edit' session.pk %}" class="btn btn-default btn-block wger-modal-dialog">{% trans "Edit workout session" %}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'manager:session:add' first_log.workout_id date|date:'Y' date|date:'m' date|date:'d' %}" class="btn btn-default btn-block wger-modal-dialog">{% trans "Add workout impression" %}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if session %}
|
||||
<table class="table" style="margin-top: 2em;">
|
||||
<tr>
|
||||
<th style="width: 30%">{% trans "Time" %}</th>
|
||||
<td>
|
||||
{% if session.time_start %}
|
||||
{{session.time_start|time:"H:i"}} - {{session.time_end|time:"H:i"}}
|
||||
{% else %}
|
||||
-/-
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans "General impression" %}</th>
|
||||
<td>{% trans session.get_impression_display %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans "Notes" %}</th>
|
||||
<td>{{session.notes|default:'-/-'}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
<h3>
|
||||
{{date}}
|
||||
<small><a href="{% url 'manager:workout:calendar-day' owner_user date.year date.month date.day %}">{% trans 'Detail page' %}</a></small>
|
||||
</h3>
|
||||
|
||||
{% include 'calendar/partial_overview_table.html' %}
|
||||
|
||||
<div class="row" style="margin-top: 2em;">
|
||||
{% for i in grouped_logs %}
|
||||
{% for exercise, logs in value.logs.items %}
|
||||
<div class="col-sm-4">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">{{ i.grouper }}</div>
|
||||
<div class="panel-heading">{{ exercise }}</div>
|
||||
<div class="panel-body" style="min-height: 8em;">
|
||||
<ul>
|
||||
{% for log in i.list %}
|
||||
{% for log in logs %}
|
||||
<li>
|
||||
{{log.reps}} × {{log.weight}}
|
||||
{% if is_owner %}
|
||||
@@ -142,7 +92,6 @@ $(document).ready(function() {
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endcache %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -188,9 +137,9 @@ day in your current workout:{% endblocktrans %} <strong>{{current_workout}}</str
|
||||
you can click on them to see the details.{% endblocktrans %}</p>
|
||||
|
||||
<p>
|
||||
<div style="background-color: #C9302C; display: inline;"> </div> {{ impressions.0.1 }}<br>
|
||||
<div style="background-color: #EC971F; display: inline;"> </div> {{ impressions.1.1 }}<br>
|
||||
<div style="background-color: #449D44; display: inline;"> </div> {{ impressions.2.1 }}
|
||||
<span style="background-color: #C9302C; display: inline;"> </span> {{ impressions.0.1 }}<br>
|
||||
<span style="background-color: #EC971F; display: inline;"> </span> {{ impressions.1.1 }}<br>
|
||||
<span style="background-color: #449D44; display: inline;"> </span> {{ impressions.2.1 }}
|
||||
</p>
|
||||
|
||||
<h3>{% trans 'Log' %}</h3>
|
||||
43
wger/manager/templates/calendar/partial_overview_table.html
Normal file
43
wger/manager/templates/calendar/partial_overview_table.html
Normal file
@@ -0,0 +1,43 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<a href="{{value.workout.get_absolute_url}}" class="btn btn-default btn-block">{{ value.workout }}</a>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<a href="{% url 'manager:log:log' value.workout.id %}" class="btn btn-default btn-block">{% trans "Log overview" %}</a>
|
||||
</div>
|
||||
|
||||
{% if is_owner %}
|
||||
<div class="col-sm-4">
|
||||
{% if value.session %}
|
||||
<a href="{% url 'manager:session:edit' value.session.pk %}" class="btn btn-default btn-block wger-modal-dialog">{% trans "Edit workout session" %}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'manager:session:add' value.workout.id date|date:'Y' date|date:'m' date|date:'d' %}" class="btn btn-default btn-block wger-modal-dialog">{% trans "Add workout impression" %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if value.session %}
|
||||
<table class="table" style="margin-top: 2em;">
|
||||
<tr>
|
||||
<th style="width: 30%">{% trans "Time" %}</th>
|
||||
<td>
|
||||
{% if value.session.time_start %}
|
||||
{{value.session.time_start|time:"H:i"}} - {{value.session.time_end|time:"H:i"}}
|
||||
{% else %}
|
||||
-/-
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans "General impression" %}</th>
|
||||
<td>{% trans value.session.get_impression_display %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans "Notes" %}</th>
|
||||
<td>{{value.session.notes|default:'-/-'}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endif %}
|
||||
@@ -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 %}
|
||||
<div class="modal fade" id="log-popup-{{log.pk}}">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
@@ -92,71 +86,34 @@ $(document).ready(function() {
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<div id="log-{{date|date:'Y-m-d'}}" class="calendar-log-data">
|
||||
<h3>{{date}}</h3>
|
||||
{% for date, value in logs.items %}
|
||||
<div id="log-{{date|date:'Y-m-d'}}" class="calendar-log-data">
|
||||
|
||||
<h3>
|
||||
{{date}}
|
||||
<small><a href="{% url 'manager:workout:calendar-day' owner_user date.year date.month date.day %}">{% trans 'Detail page' %}</a></small>
|
||||
</h3>
|
||||
|
||||
{% include 'calendar/partial_overview_table.html' %}
|
||||
|
||||
|
||||
{# 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 %}
|
||||
|
||||
<a href="{{first_log.workout.get_absolute_url}}"
|
||||
class="btn btn-default btn-block">{{ first_log.workout }}</a>
|
||||
|
||||
<a href="{% url 'manager:log:log' first_log.workout.id %}"
|
||||
class="btn btn-default btn-block">{% trans "Log overview" %}</a>
|
||||
|
||||
{% if is_owner %}
|
||||
{% if session %}
|
||||
<a href="{% url 'manager:session:edit' session.pk %}"
|
||||
class="btn btn-default btn-block wger-modal-dialog">{% trans "Edit workout session" %}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'manager:session:add' first_log.workout_id date|date:'Y' date|date:'m' date|date:'d' %}"
|
||||
class="btn btn-default btn-block wger-modal-dialog">{% trans "Add workout impression" %}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if session %}
|
||||
<table class="table" style="margin-top: 2em;">
|
||||
<tr>
|
||||
<th style="width: 30%">{% trans "Time" %}</th>
|
||||
<td>
|
||||
{% if session.time_start %}
|
||||
{{session.time_start|time:"H:i"}} - {{session.time_end|time:"H:i"}}
|
||||
{% else %}
|
||||
-/-
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans "General impression" %}</th>
|
||||
<td>{% trans session.get_impression_display %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans "Notes" %}</th>
|
||||
<td>{{session.notes|default:'-/-'}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
|
||||
{# #}
|
||||
{# Logs #}
|
||||
{# #}
|
||||
<div class="row" style="margin-top: 2em;">
|
||||
{% for i in grouped_logs %}
|
||||
|
||||
{% for exercise, logs in value.logs.items %}
|
||||
|
||||
<div class="col-sm-4">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">{{ i.grouper }}</div>
|
||||
<div class="panel-heading">{{ exercise }}</div>
|
||||
<div class="panel-body" style="min-height: 8em;">
|
||||
<ul>
|
||||
{% for log in i.list %}
|
||||
{% for log in logs %}
|
||||
<li>
|
||||
{% if is_owner %}
|
||||
<a href="#log-popup-{{log.pk}}" data-toggle="modal">
|
||||
@@ -176,7 +133,6 @@ $(document).ready(function() {
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% endcache %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -225,9 +181,9 @@ day in your current workout:{% endblocktrans %} {{current_workout}}</p>
|
||||
you can click on them to see the details.{% endblocktrans %}</p>
|
||||
|
||||
<p>
|
||||
<div style="background-color: #C9302C; display: inline;"> </div> {{ impressions.0.1 }}<br>
|
||||
<div style="background-color: #EC971F; display: inline;"> </div> {{ impressions.1.1 }}<br>
|
||||
<div style="background-color: #449D44; display: inline;"> </div> {{ impressions.2.1 }}
|
||||
<span style="background-color: #C9302C; display: inline;"> </span> {{ impressions.0.1 }}<br>
|
||||
<span style="background-color: #EC971F; display: inline;"> </span> {{ impressions.1.1 }}<br>
|
||||
<span style="background-color: #449D44; display: inline;"> </span> {{ impressions.2.1 }}
|
||||
</p>
|
||||
|
||||
<h3>{% trans 'Log' %}</h3>
|
||||
@@ -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) {
|
||||
|
||||
@@ -375,94 +375,133 @@ class WorkoutLogCacheTestCase(WorkoutManagerTestCase):
|
||||
|
||||
def test_calendar(self):
|
||||
'''
|
||||
Test the exercise overview cache is correctly generated on visit
|
||||
Test the log cache is correctly generated on visit
|
||||
'''
|
||||
log_hash = hash((1, 2012, 10))
|
||||
self.user_login('admin')
|
||||
self.assertFalse(cache.get(cache_mapper.get_workout_log(1, 2012, 10)))
|
||||
for cache_key in ('workout-log-full', 'workout-log-mobile'):
|
||||
self.assertFalse(cache.get(get_template_cache_name(cache_key, True, 1, 2012, 10)))
|
||||
self.client.get(reverse('manager:workout:calendar', kwargs={'year': 2012, 'month': 10}))
|
||||
self.assertFalse(cache.get(cache_mapper.get_workout_log_list(log_hash)))
|
||||
|
||||
cache_key = 'workout-log-mobile' if self.is_mobile else 'workout-log-full'
|
||||
self.assertTrue(cache.get(get_template_cache_name(cache_key, True, 1, 2012, 10)))
|
||||
self.assertTrue(cache.get(cache_mapper.get_workout_log(1, 2012, 10)))
|
||||
self.client.get(reverse('manager:workout:calendar', kwargs={'year': 2012, 'month': 10}))
|
||||
self.assertTrue(cache.get(cache_mapper.get_workout_log_list(log_hash)))
|
||||
|
||||
def test_calendar_day(self):
|
||||
'''
|
||||
Test the log cache on the calendar day view is correctly generated on visit
|
||||
'''
|
||||
log_hash = hash((1, 2012, 10, 1))
|
||||
self.user_login('admin')
|
||||
self.assertFalse(cache.get(cache_mapper.get_workout_log_list(log_hash)))
|
||||
|
||||
self.client.get(reverse('manager:workout:calendar-day', kwargs={'username': 'admin',
|
||||
'year': 2012,
|
||||
'month': 10,
|
||||
'day': 1}))
|
||||
self.assertTrue(cache.get(cache_mapper.get_workout_log_list(log_hash)))
|
||||
|
||||
def test_calendar_anonymous(self):
|
||||
'''
|
||||
Test the exercise overview cache is correctly generated on visit
|
||||
by anonymous users
|
||||
Test the log cache is correctly generated on visit by anonymous users
|
||||
'''
|
||||
log_hash = hash((1, 2012, 10))
|
||||
self.user_logout()
|
||||
self.assertFalse(cache.get(cache_mapper.get_workout_log(1, 2012, 10)))
|
||||
for cache_key in ('workout-log-full', 'workout-log-mobile'):
|
||||
self.assertFalse(cache.get(get_template_cache_name(cache_key, True, 1, 2012, 10)))
|
||||
self.assertFalse(cache.get(get_template_cache_name(cache_key, False, 1, 2012, 10)))
|
||||
self.assertFalse(cache.get(cache_mapper.get_workout_log_list(log_hash)))
|
||||
|
||||
self.client.get(reverse('manager:workout:calendar', kwargs={'username': 'admin',
|
||||
'year': 2012,
|
||||
'month': 10}))
|
||||
self.assertTrue(cache.get(cache_mapper.get_workout_log_list(log_hash)))
|
||||
|
||||
cache_key = 'workout-log-mobile' if self.is_mobile else 'workout-log-full'
|
||||
self.assertTrue(cache.get(cache_mapper.get_workout_log(1, 2012, 10)))
|
||||
self.assertTrue(cache.get(get_template_cache_name(cache_key, False, 1, 2012, 10)))
|
||||
self.assertFalse(cache.get(get_template_cache_name(cache_key, True, 1, 2012, 10)))
|
||||
def test_calendar_day_anonymous(self):
|
||||
'''
|
||||
Test the log cache is correctly generated on visit by anonymous users
|
||||
'''
|
||||
log_hash = hash((1, 2012, 10, 1))
|
||||
self.user_logout()
|
||||
self.assertFalse(cache.get(cache_mapper.get_workout_log_list(log_hash)))
|
||||
|
||||
self.client.get(reverse('manager:workout:calendar-day', kwargs={'username': 'admin',
|
||||
'year': 2012,
|
||||
'month': 10,
|
||||
'day': 1}))
|
||||
self.assertTrue(cache.get(cache_mapper.get_workout_log_list(log_hash)))
|
||||
|
||||
def test_cache_update_log(self):
|
||||
'''
|
||||
Test that the caches are cleared when saving a log
|
||||
'''
|
||||
log_hash = hash((1, 2012, 10))
|
||||
log_hash_day = hash((1, 2012, 10, 1))
|
||||
self.user_login('admin')
|
||||
self.client.get(reverse('manager:workout:calendar', kwargs={'year': 2012, 'month': 10}))
|
||||
self.client.get(reverse('manager:workout:calendar-day', kwargs={'username': 'admin',
|
||||
'year': 2012,
|
||||
'month': 10,
|
||||
'day': 1}))
|
||||
|
||||
log = WorkoutLog.objects.get(pk=1)
|
||||
log.weight = 35
|
||||
log.save()
|
||||
|
||||
cache_key = 'workout-log-mobile' if self.is_mobile else 'workout-log-full'
|
||||
self.assertFalse(cache.get(cache_mapper.get_workout_log(1, 2012, 10)))
|
||||
self.assertFalse(cache.get(get_template_cache_name(cache_key, True, 1, 2012, 10)))
|
||||
self.assertFalse(cache.get(cache_mapper.get_workout_log_list(log_hash)))
|
||||
self.assertFalse(cache.get(cache_mapper.get_workout_log_list(log_hash_day)))
|
||||
|
||||
def test_cache_update_log_2(self):
|
||||
'''
|
||||
Test that the caches are only cleared for a the log's month
|
||||
'''
|
||||
log_hash = hash((1, 2012, 10))
|
||||
log_hash_day = hash((1, 2012, 10, 1))
|
||||
self.user_login('admin')
|
||||
self.client.get(reverse('manager:workout:calendar', kwargs={'year': 2012, 'month': 10}))
|
||||
self.client.get(reverse('manager:workout:calendar-day', kwargs={'username': 'admin',
|
||||
'year': 2012,
|
||||
'month': 10,
|
||||
'day': 1}))
|
||||
|
||||
log = WorkoutLog.objects.get(pk=3)
|
||||
log.weight = 35
|
||||
log.save()
|
||||
|
||||
cache_key = 'workout-log-mobile' if self.is_mobile else 'workout-log-full'
|
||||
self.assertTrue(cache.get(cache_mapper.get_workout_log(1, 2012, 10)))
|
||||
self.assertTrue(cache.get(get_template_cache_name(cache_key, True, 1, 2012, 10)))
|
||||
self.assertTrue(cache.get(cache_mapper.get_workout_log_list(log_hash)))
|
||||
self.assertTrue(cache.get(cache_mapper.get_workout_log_list(log_hash_day)))
|
||||
|
||||
def test_cache_delete_log(self):
|
||||
'''
|
||||
Test that the caches are cleared when deleting a log
|
||||
'''
|
||||
log_hash = hash((1, 2012, 10))
|
||||
log_hash_day = hash((1, 2012, 10, 1))
|
||||
self.user_login('admin')
|
||||
self.client.get(reverse('manager:workout:calendar', kwargs={'year': 2012, 'month': 10}))
|
||||
self.client.get(reverse('manager:workout:calendar-day', kwargs={'username': 'admin',
|
||||
'year': 2012,
|
||||
'month': 10,
|
||||
'day': 1}))
|
||||
|
||||
log = WorkoutLog.objects.get(pk=1)
|
||||
log.delete()
|
||||
|
||||
cache_key = 'workout-log-mobile' if self.is_mobile else 'workout-log-full'
|
||||
self.assertFalse(cache.get(cache_mapper.get_workout_log(1, 2012, 10)))
|
||||
self.assertFalse(cache.get(get_template_cache_name(cache_key, True, 1, 2012, 10)))
|
||||
self.assertFalse(cache.get(cache_mapper.get_workout_log_list(log_hash)))
|
||||
self.assertFalse(cache.get(cache_mapper.get_workout_log_list(log_hash_day)))
|
||||
|
||||
def test_cache_delete_log_2(self):
|
||||
'''
|
||||
Test that the caches are only cleared for a the log's month
|
||||
'''
|
||||
log_hash = hash((1, 2012, 10))
|
||||
log_hash_day = hash((1, 2012, 10, 1))
|
||||
self.user_login('admin')
|
||||
self.client.get(reverse('manager:workout:calendar', kwargs={'year': 2012, 'month': 10}))
|
||||
self.client.get(reverse('manager:workout:calendar-day', kwargs={'username': 'admin',
|
||||
'year': 2012,
|
||||
'month': 10,
|
||||
'day': 1}))
|
||||
|
||||
log = WorkoutLog.objects.get(pk=3)
|
||||
log.delete()
|
||||
|
||||
cache_key = 'workout-log-mobile' if self.is_mobile else 'workout-log-full'
|
||||
self.assertTrue(cache.get(cache_mapper.get_workout_log(1, 2012, 10)))
|
||||
self.assertTrue(cache.get(get_template_cache_name(cache_key, True, 1, 2012, 10)))
|
||||
self.assertTrue(cache.get(cache_mapper.get_workout_log_list(log_hash)))
|
||||
self.assertTrue(cache.get(cache_mapper.get_workout_log_list(log_hash_day)))
|
||||
|
||||
|
||||
class WorkoutLogApiTestCase(api_base_test.ApiBaseResourceTestCase):
|
||||
|
||||
@@ -141,6 +141,7 @@ class WorkoutLogCacheTestCase(WorkoutManagerTestCase):
|
||||
'''
|
||||
Test that the caches are cleared when updating a workout session
|
||||
'''
|
||||
log_hash = hash((1, 2012, 10))
|
||||
self.user_login('admin')
|
||||
self.client.get(reverse('manager:workout:calendar', kwargs={'year': 2012, 'month': 10}))
|
||||
|
||||
@@ -148,14 +149,13 @@ class WorkoutLogCacheTestCase(WorkoutManagerTestCase):
|
||||
session.notes = 'Lorem ipsum'
|
||||
session.save()
|
||||
|
||||
cache_key = 'workout-log-mobile' if self.is_mobile else 'workout-log-full'
|
||||
self.assertFalse(cache.get(cache_mapper.get_workout_log(1, 2012, 10)))
|
||||
self.assertFalse(cache.get(get_template_cache_name(cache_key, True, 1, 2012, 10)))
|
||||
self.assertFalse(cache.get(cache_mapper.get_workout_log_list(log_hash)))
|
||||
|
||||
def test_cache_update_session_2(self):
|
||||
'''
|
||||
Test that the caches are only cleared for a the session's month
|
||||
'''
|
||||
log_hash = hash((1, 2012, 10))
|
||||
self.user_login('admin')
|
||||
self.client.get(reverse('manager:workout:calendar', kwargs={'year': 2012, 'month': 10}))
|
||||
|
||||
@@ -164,37 +164,33 @@ class WorkoutLogCacheTestCase(WorkoutManagerTestCase):
|
||||
session.notes = 'Lorem ipsum'
|
||||
session.save()
|
||||
|
||||
cache_key = 'workout-log-mobile' if self.is_mobile else 'workout-log-full'
|
||||
self.assertTrue(cache.get(cache_mapper.get_workout_log(1, 2012, 10)))
|
||||
self.assertTrue(cache.get(get_template_cache_name(cache_key, True, 1, 2012, 10)))
|
||||
self.assertTrue(cache.get(cache_mapper.get_workout_log_list(log_hash)))
|
||||
|
||||
def test_cache_delete_session(self):
|
||||
'''
|
||||
Test that the caches are cleared when deleting a workout session
|
||||
'''
|
||||
log_hash = hash((1, 2012, 10))
|
||||
self.user_login('admin')
|
||||
self.client.get(reverse('manager:workout:calendar', kwargs={'year': 2012, 'month': 10}))
|
||||
|
||||
session = WorkoutSession.objects.get(pk=1)
|
||||
session.delete()
|
||||
|
||||
cache_key = 'workout-log-mobile' if self.is_mobile else 'workout-log-full'
|
||||
self.assertFalse(cache.get(cache_mapper.get_workout_log(1, 2012, 10)))
|
||||
self.assertFalse(cache.get(get_template_cache_name(cache_key, 'True', 1, 2012, 10)))
|
||||
self.assertFalse(cache.get(cache_mapper.get_workout_log_list(log_hash)))
|
||||
|
||||
def test_cache_delete_session_2(self):
|
||||
'''
|
||||
Test that the caches are only cleared for a the session's month
|
||||
'''
|
||||
log_hash = hash((1, 2012, 10))
|
||||
self.user_login('admin')
|
||||
self.client.get(reverse('manager:workout:calendar', kwargs={'year': 2012, 'month': 10}))
|
||||
|
||||
session = WorkoutSession.objects.get(pk=2)
|
||||
session.delete()
|
||||
|
||||
cache_key = 'workout-log-mobile' if self.is_mobile else 'workout-log-full'
|
||||
self.assertTrue(cache.get(cache_mapper.get_workout_log(1, 2012, 10)))
|
||||
self.assertTrue(cache.get(get_template_cache_name(cache_key, True, 1, 2012, 10)))
|
||||
self.assertTrue(cache.get(cache_mapper.get_workout_log_list(log_hash)))
|
||||
|
||||
|
||||
class WorkoutSessionApiTestCase(api_base_test.ApiBaseResourceTestCase):
|
||||
|
||||
@@ -77,6 +77,9 @@ patterns_workout = patterns('',
|
||||
url(r'^calendar/(?P<year>\d{4})/(?P<month>\d{1,2})$',
|
||||
log.calendar,
|
||||
name='calendar'),
|
||||
url(r'^calendar/(?P<username>[\w.@+-]+)/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})$',
|
||||
log.day,
|
||||
name='calendar-day'),
|
||||
url(r'^(?P<pk>\d+)/ical/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[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")),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -17,13 +17,11 @@
|
||||
import logging
|
||||
import uuid
|
||||
import datetime
|
||||
from calendar import HTMLCalendar
|
||||
|
||||
from django.shortcuts import render
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.http import HttpResponseForbidden
|
||||
from django.core.cache import cache
|
||||
from django.core.context_processors import csrf
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
@@ -36,6 +34,7 @@ from django.views.generic import CreateView
|
||||
from django.views.generic import DetailView
|
||||
from django.views.generic import DeleteView
|
||||
|
||||
from wger.manager.helpers import WorkoutCalendar
|
||||
from wger.manager.models import Workout
|
||||
from wger.manager.models import WorkoutSession
|
||||
from wger.manager.models import Day
|
||||
@@ -44,12 +43,12 @@ from wger.manager.models import Schedule
|
||||
from wger.manager.forms import HelperDateForm
|
||||
from wger.manager.forms import HelperWorkoutSessionForm
|
||||
from wger.manager.forms import WorkoutLogForm
|
||||
from wger.utils.cache import cache_mapper
|
||||
from wger.utils.generic_views import WgerFormMixin
|
||||
from wger.utils.generic_views import WgerDeleteMixin
|
||||
from wger.utils.generic_views import WgerPermissionMixin
|
||||
from wger.utils.helpers import check_access, make_token
|
||||
from wger.weight.helpers import process_log_entries
|
||||
from wger.weight.helpers import process_log_entries, group_log_entries
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -316,106 +315,20 @@ class WorkoutLogDetailView(DetailView, WgerPermissionMixin):
|
||||
return super(WorkoutLogDetailView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
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):
|
||||
if day != 0:
|
||||
cssclass = self.cssclasses[weekday]
|
||||
date_obj = datetime.date(self.year, self.month, day)
|
||||
if datetime.date.today() == date_obj:
|
||||
cssclass += ' today'
|
||||
if day in self.workout_logs:
|
||||
current_log = self.workout_logs.get(day)
|
||||
if current_log['impression'] == WorkoutSession.IMPRESSION_BAD:
|
||||
background_css = 'btn-danger'
|
||||
elif current_log['impression'] == WorkoutSession.IMPRESSION_GOOD:
|
||||
background_css = 'btn-success'
|
||||
else:
|
||||
background_css = 'btn-warning'
|
||||
|
||||
url = reverse('manager:log:log', kwargs={'pk': current_log['log'].workout_id})
|
||||
formatted_date = date_obj.strftime('%Y-%m-%d')
|
||||
body = []
|
||||
body.append('<a href="{0}"'
|
||||
'data-log="log-{1}"'
|
||||
'class="btn btn-block {2} calendar-link">'.format(url,
|
||||
formatted_date,
|
||||
background_css))
|
||||
body.append(repr(day))
|
||||
body.append('</a>')
|
||||
return self.day_cell(cssclass, '{0}'.format(''.join(body)))
|
||||
return self.day_cell(cssclass, day)
|
||||
return self.day_cell('noday', ' ')
|
||||
|
||||
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('<table class="month table table-bordered">\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('</table>\n')
|
||||
return ''.join(out)
|
||||
|
||||
def day_cell(self, cssclass, body):
|
||||
'''
|
||||
Renders a day cell
|
||||
'''
|
||||
return '<td class="{0}" style="vertical-align: middle;">{1}</td>'.format(cssclass, body)
|
||||
|
||||
|
||||
def calendar(request, username=None, year=None, month=None):
|
||||
'''
|
||||
Show a calendar with all the workout logs
|
||||
'''
|
||||
context = {}
|
||||
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
|
||||
|
||||
context = {}
|
||||
logs = WorkoutLog.objects.filter(user=user,
|
||||
date__year=year,
|
||||
date__month=month).order_by('exercise')
|
||||
logs_filtered = cache.get(cache_mapper.get_workout_log(user.pk, year, month))
|
||||
if not logs_filtered:
|
||||
logs_filtered = {}
|
||||
|
||||
# Process the logs. Group by date and check for impressions
|
||||
for log in logs:
|
||||
if log.date not in logs_filtered:
|
||||
session = log.get_workout_session()
|
||||
|
||||
if session:
|
||||
impression = session.impression
|
||||
else:
|
||||
# Default is 'neutral'
|
||||
impression = WorkoutSession.IMPRESSION_NEUTRAL
|
||||
|
||||
logs_filtered[log.date.day] = {'impression': impression,
|
||||
'log': log}
|
||||
cache.set(cache_mapper.get_workout_log(user.pk, year, month), logs_filtered)
|
||||
|
||||
(current_workout, schedule) = Schedule.objects.get_current_workout(user)
|
||||
context['calendar'] = WorkoutCalendar(logs_filtered).formatmonth(year, month)
|
||||
context['logs'] = process_log_entries(logs)[0]
|
||||
grouped_log_entries = group_log_entries(user, year, month)
|
||||
|
||||
context['calendar'] = WorkoutCalendar(grouped_log_entries).formatmonth(year, month)
|
||||
context['logs'] = grouped_log_entries
|
||||
context['current_year'] = year
|
||||
context['current_month'] = month
|
||||
context['current_workout'] = current_workout
|
||||
@@ -424,4 +337,25 @@ 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):
|
||||
'''
|
||||
Show the logs for a single day
|
||||
'''
|
||||
context = {}
|
||||
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()
|
||||
context['logs'] = group_log_entries(user, date.year, date.month, date.day)
|
||||
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, 'calendar/day.html', context)
|
||||
|
||||
@@ -124,21 +124,21 @@ def export_pdf(request, pk, uidb64=None, token=None):
|
||||
# Set the title
|
||||
p = Paragraph(u'<para align="center">{0}</para>'.format(schedule), 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 step in schedule.schedulestep_set.all():
|
||||
p = Paragraph(u'<para>{0} {1}</para>'.format(step.duration, _('Weeks')),
|
||||
styleSheet["HeaderBold"])
|
||||
elements.append(p)
|
||||
elements.append(Spacer(10*cm, 0.5*cm))
|
||||
elements.append(Spacer(10 * cm, 0.5 * cm))
|
||||
|
||||
for day in step.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))
|
||||
url = reverse('manager:schedule:view', kwargs={'pk': schedule.id})
|
||||
elements.append(render_footer(request.build_absolute_uri(url)))
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ def chart_data(request):
|
||||
|
||||
{'key': 'obese_class_3', 'height': 150, 'weight': 190},
|
||||
{'key': 'obese_class_3', 'height': 200, 'weight': 190}
|
||||
])
|
||||
])
|
||||
|
||||
# Return the results to the client
|
||||
return HttpResponse(data, 'application/json')
|
||||
|
||||
@@ -300,7 +300,7 @@ def export_pdf(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 to the document
|
||||
elements.append(t)
|
||||
@@ -356,7 +356,7 @@ def export_pdf(request, id, uidb64=None, token=None):
|
||||
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")
|
||||
url = reverse('nutrition:plan:view', kwargs={'id': plan.id})
|
||||
p = Paragraph('''<para align="left">
|
||||
|
||||
@@ -267,7 +267,7 @@ THUMBNAIL_ALIASES = {
|
||||
|
||||
'large': {'size': (800, 800), 'quality': 90},
|
||||
'large_cropped': {'size': (800, 800), 'crop': 'smart', 'quality': 90},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
<a href="https://github.com/rolandgeider/wger/issues/108">#100</a>
|
||||
<a href="https://github.com/rolandgeider/wger/issues/108">#108</a>
|
||||
<a href="https://github.com/rolandgeider/wger/issues/117">#117</a>
|
||||
<a href="https://github.com/rolandgeider/wger/issues/131">#131</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -43,12 +43,16 @@ def reset_workout_canonical_form(workout_id):
|
||||
cache.delete(cache_mapper.get_workout_canonical(workout_id))
|
||||
|
||||
|
||||
def reset_workout_log(user_pk, year, month):
|
||||
delete_template_fragment_cache('workout-log-full', True, user_pk, year, month)
|
||||
delete_template_fragment_cache('workout-log-full', False, user_pk, year, month)
|
||||
delete_template_fragment_cache('workout-log-mobile', True, user_pk, year, month)
|
||||
delete_template_fragment_cache('workout-log-mobile', False, user_pk, year, month)
|
||||
cache.delete(cache_mapper.get_workout_log(user_pk, year, month))
|
||||
def reset_workout_log(user_pk, year, month, day=None):
|
||||
'''
|
||||
Resets the cached workout logs
|
||||
'''
|
||||
|
||||
log_hash = hash((user_pk, year, month))
|
||||
cache.delete(cache_mapper.get_workout_log_list(log_hash))
|
||||
|
||||
log_hash = hash((user_pk, year, month, day))
|
||||
cache.delete(cache_mapper.get_workout_log_list(log_hash))
|
||||
|
||||
|
||||
class CacheKeyMapper(object):
|
||||
@@ -63,7 +67,7 @@ class CacheKeyMapper(object):
|
||||
EXERCISE_CACHE_KEY_MUSCLE_BG = 'exercise-muscle-bg-{0}'
|
||||
INGREDIENT_CACHE_KEY = 'ingredient-{0}'
|
||||
WORKOUT_CANONICAL_REPRESENTATION = 'workout-canonical-representation-{0}'
|
||||
WORKOUT_LOG = 'workout-log-user{0}-year{1}-month{2}'
|
||||
WORKOUT_LOG_LIST = 'workout-log-hash-{0}'
|
||||
|
||||
def get_exercise_key(self, param):
|
||||
'''
|
||||
@@ -131,10 +135,10 @@ class CacheKeyMapper(object):
|
||||
|
||||
return self.WORKOUT_CANONICAL_REPRESENTATION.format(pk)
|
||||
|
||||
def get_workout_log(self, user_pk, year, month):
|
||||
def get_workout_log_list(self, hash_value):
|
||||
'''
|
||||
Return the workout canonical representation
|
||||
'''
|
||||
return self.WORKOUT_LOG.format(user_pk, year, month)
|
||||
return self.WORKOUT_LOG_LIST.format(hash_value)
|
||||
|
||||
cache_mapper = CacheKeyMapper()
|
||||
|
||||
@@ -45,8 +45,8 @@ def processor(request):
|
||||
'request_full_path': full_path,
|
||||
|
||||
# The current full path with host
|
||||
'request_absolute_path': request.build_absolute_uri(),
|
||||
'image_absolute_path': request.build_absolute_uri(static_path),
|
||||
'request_absolute_path': request.build_absolute_uri(),
|
||||
'image_absolute_path': request.build_absolute_uri(static_path),
|
||||
|
||||
|
||||
# Translation links
|
||||
|
||||
@@ -47,4 +47,4 @@ class WeightForm(ModelForm):
|
||||
exclude = []
|
||||
widgets = {
|
||||
'user': widgets.HiddenInput(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,13 @@ import csv
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
|
||||
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__)
|
||||
|
||||
@@ -56,6 +61,69 @@ def parse_weight_csv(request, cleaned_data):
|
||||
return (weight_list, error_list)
|
||||
|
||||
|
||||
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 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
|
||||
'''
|
||||
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
|
||||
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)
|
||||
|
||||
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,
|
||||
'workout': entry.workout,
|
||||
'session': entry.get_workout_session(),
|
||||
'logs': {}}
|
||||
|
||||
if not out[entry.date]['logs'].get(entry.exercise):
|
||||
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
|
||||
|
||||
|
||||
def process_log_entries(logs):
|
||||
'''
|
||||
Processes and regroups a list of log entries so they can be rendered
|
||||
@@ -96,10 +164,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]:
|
||||
|
||||
Reference in New Issue
Block a user