Merge branch 'master' of github.com:wger-project/wger

This commit is contained in:
Roland Geider
2016-06-17 13:10:07 +02:00
23 changed files with 256 additions and 7323 deletions

View File

@@ -9,6 +9,8 @@ Upgrade steps:
* Database upgrade: ``python manage.py migrate``
* Reset workout cache: ``python manage.py clear-cache --clear-workout-cache``
* New config option in settings.py: ``WGER_SETTINGS['TWITTER']``. Set this if
your instance has its own twitter account.
* Update static files (only production): ``python manage.py collectstatic``
New features:
@@ -17,12 +19,16 @@ New features:
* Use the metricsgraphics library to more easily draw charts `#188`_
* Add extended PDF options to schedules as well (thanks `@alelevinas`_ ) `#272`_
* Show trained secondary muscles in workout view (thanks `@alokhan`_ ) `#282`_
Improvements:
* Replace jquery UI's autocompleter and sortable this reduces size of JS and CSS `#78`_
* Remove hard-coded CC licence from documentation and website `#247`_
Other improvements and bugfixes: `#279`_, `#275`_, `#270`_, `#258`_, `#257`_, `#269`_
Other improvements and bugfixes: `#279`_, `#275`_, `#270`_, `#258`_, `#257`_, `#269`_, `#296_`
.. _#78: https://github.com/wger-project/wger/issues/78
.. _#188: https://github.com/wger-project/wger/issues/188
.. _#216: https://github.com/wger-project/wger/issues/216
.. _#217: https://github.com/wger-project/wger/issues/217
@@ -35,6 +41,7 @@ Other improvements and bugfixes: `#279`_, `#275`_, `#270`_, `#258`_, `#257`_, `#
.. _#275: https://github.com/wger-project/wger/issues/275
.. _#279: https://github.com/wger-project/wger/issues/279
.. _#282: https://github.com/wger-project/wger/issues/282
.. _#296: https://github.com/wger-project/wger/issues/296
.. _@alelevinas: https://github.com/alelevinas
.. _@alokhan: https://github.com/alokhan
@@ -377,4 +384,4 @@ New features and bugfixes:
* Simple weight chart
* Nutrition plan manager
* Simple PDF output
* Initial data with nutritional values from the USDA
* Initial data with nutritional values from the USDA

View File

@@ -25,7 +25,7 @@ setup(
description='FLOSS workout, fitness and weight manager/tracker written with Django',
long_description=long_description,
version=get_version(),
url='https://wger.de',
url='https://github.com/wger-project',
author='Roland Geider',
author_email='roland@geider.net',
license='AGPL3+',

File diff suppressed because it is too large Load Diff

View File

@@ -1,37 +0,0 @@
/*
This file is part of Workout Manager
Workout Manager is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Workout Manager is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
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/>.
*/
/**************************************************************
*
* This file contains slight modifications to the CSS provided
* by JQuery UI
*
**************************************************************/
/* Add a max-height for the autocompleter */
.ui-autocomplete {
max-height: 300px;
overflow-y: auto;
overflow-x: hidden; /* prevent horizontal scrollbar */
}
/* Increase the z-index so bootstrap's modals, etc. don't interfere */
.ui-front {
z-index: 9999;
}

File diff suppressed because one or more lines are too long

View File

@@ -329,4 +329,62 @@ table.month td.session-good {
table.month td.session-good:hover {
background-color: #449D44;
}
}
/*
* Autocompleter
*/
.autocomplete-wrapper {
margin: 44px auto 44px;
max-width: 600px;
}
.autocomplete-wrapper label {
display: block;
margin-bottom: .75em;
color: #3f4e5e;
font-size: 1.25em;
}
.autocomplete-wrapper .text-field {
padding: 0 15px;
width: 100%;
height: 40px;
border: 1px solid #CBD3DD;
font-size: 1.125em;
}
.autocomplete-wrapper ::-webkit-input-placeholder,
.autocomplete-wrapper :-moz-placeholder,
.autocomplete-wrapper ::-moz-placeholder,
.autocomplete-wrapper :-ms-input-placeholder {
color: #CBD3DD;
font-style: italic;
font-size: 18px;
}
.autocomplete-suggestions {
overflow: auto;
border: 1px solid #CBD3DD;
background: #FFF; }
.autocomplete-suggestion {
overflow: hidden;
padding: 5px 15px;
white-space: nowrap;
}
.autocomplete-selected {
background: #F0F0F0;
}
.autocomplete-suggestions strong {
color: #3465a4;
font-weight: bold;
}
.autocomplete-group {
background-color: #d3d7cf;
}
.autocomplete-group strong {
color: black;
font-weight: bold;
}

2
wger/core/static/js/Sortable.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
# Latest version can be downloaded with
npm install sortablejs

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -72,70 +72,43 @@ function get_current_language() {
return $('#current-language').data('currentLanguage');
}
/*
* Define an own widget, which is basically an autocompleter that groups
* results by category
*/
$.widget("custom.catcomplete", $.ui.autocomplete, {
_renderMenu: function (ul, items) {
var that = this,
currentCategory = "";
$.each(items, function (index, item) {
if (item.category !== currentCategory) {
ul.append("<li class='ui-autocomplete-category'>" + item.category + "</li>");
currentCategory = item.category;
}
that._renderItemData(ul, item);
});
},
_renderItem: function (ul, item) {
var li_style = '';
if (item.image) {
li_style = "style='background-image:url(" + item.image_thumbnail + ");background-size:30px 30px;background-repeat:no-repeat;'";
}
return $("<li " + li_style + ">")
.append("<a style='margin-left:30px;'>" + item.name + "</a>")
.appendTo(ul);
}
});
/*
* Setup JQuery sortables to make the sets sortable
*/
function setup_sortable() {
$(".workout-table tbody").sortable({
handle: '.dragndrop-handle',
revert: true,
axis: 'y',
update : function (event, ui) {
// Monkey around the HTML, till we find the IDs of the set and the day
var day_element = ui.item.parent().parent().find('tr').first().attr('id'); //day-xy
var day_id = day_element.match(/\d+/)[0];
var elements = document.getElementsByTagName('tbody');
$.each(elements, function(index, element) {
Sortable.create(element, {
handle: '.dragndrop-handle',
animation: 150,
onUpdate: function (event) {
var day_id = $(event.target).parents('table').data('id');
$.each(($(event.from).children('tr')), function(index, tr_element) {
var tr_element = $(tr_element);
// returns something in the form "set-1,set-2,set-3,"
var order = $(this).sortable('toArray');
// The last table element has no ID attribute (has only the
// 'add exercise' link
if( tr_element.data('id') )
{
var set_id = tr_element.data('id');
$.ajax({
url:'/api/v2/set/' + set_id + '/',
type: 'PATCH',
data: {'order': index + 1}
}).done(function(data) {
//console.log(data);
});
}
});
$.each(order, function (index, value) {
if (value) {
var set_pk = value.match(/\d+/)[0];
$.ajax({
url:'/api/v2/set/' + set_pk + '/',
type: 'PATCH',
data: {'order': index + 1}
}).done(function(data) {
//console.log(data);
});
}
});
// TODO: it seems to be necessary to call the view two times before it returns
// current data.
$.get('/' + get_current_language() + "/workout/day/" + day_id + "/view/");
$("#div-day-" + day_id).load('/' + get_current_language() + "/workout/day/" + day_id + "/view/");
}
// Replace the content of the table with a fresh version that has
// correct indexes.
$.get('/' + get_current_language() + "/workout/day/" + day_id + "/view/");
$("#div-day-" + day_id).load('/' + get_current_language() + "/workout/day/" + day_id + "/view/");
}
});
});
}
@@ -508,23 +481,36 @@ function init_remove_exercise_formset() {
function init_edit_set() {
// Initialise the autocompleter (our widget, defined above)
if (jQuery.ui) {
$("#exercise-search").catcomplete({
source: '/api/v2/exercise/search/?language=' + get_current_language(),
minLength: 2,
select: function (event, ui) {
$('#exercise-search').devbridgeAutocomplete({
serviceUrl: '/api/v2/exercise/search/?language=' + get_current_language(),
onSelect: function (suggestion) {
// Add the exercise to the list
add_exercise({id: suggestion.data.id,
value: suggestion.value});
// Add the exercise to the list
add_exercise(ui.item);
// Load formsets
get_exercise_formset(suggestion.data.id);
// Load formsets
get_exercise_formset(ui.item.id);
// Init the remove buttons
init_remove_exercise_formset();
// Init the remove buttons
init_remove_exercise_formset();
// Reset the autocompleter
$(this).val("");
return false;
// Reset the autocompleter
$(this).val("");
return false;
},
groupBy: 'category',
paramName: 'term',
transformResult: function(response) {
// why is response not already a JSON object??
var jsonResponse = $.parseJSON(response);
return {
suggestions: $.map(jsonResponse, function(item) {
return {value: item.value, data: {id: item.id,
category: item.category,
image: item.image,
thumbnail: item.image_thumbnail}};
})
};
}
});
}
@@ -712,4 +698,4 @@ $(document).ready(function() {
'/' + token;
window.location.href = targetUrl;
});
});
});

View File

@@ -40,8 +40,10 @@
<!-- twitter cards -->
{% block twittercard %}
{% if twitter %}
<meta content="summary" name="twitter:card">
<meta content="@wger_de" name="twitter:site">
<meta content="@{{ twitter }}" name="twitter:site">
{% endif %}
{% endblock %}
<title>{% block title %}{% endblock %}</title>

View File

@@ -13,7 +13,7 @@ $(document).ready(function() {
})
</script>
<table class="table table-bordered workout-table" id="table-day-{{ day.obj.id }}">
<table class="table table-bordered workout-table" id="table-day-{{ day.obj.id }}" data-id="{{ day.obj.id }}">
<thead>
<tr id="day-{{ day.obj.id }}">
<th colspan="2" style="background: none repeat scroll 0% 0% #F5F5F5;">
@@ -47,7 +47,7 @@ $(document).ready(function() {
</thead>
<tbody>
{% for set in day.set_list %}
<tr id="set-{{ set.obj.id }}">
<tr data-id="{{ set.obj.id }}" id="set-{{ set.obj.id }}">
<td style="width: 15%;border-right-width: 0px;">
<h5><strong>{{ forloop.counter }}</strong></h5>

View File

@@ -39,16 +39,16 @@
<!-- twitter cards -->
{% block twittercard %}
{% if twitter %}
<meta content="summary" name="twitter:card">
<meta content="@wger_de" name="twitter:site">
<meta content="@{{ twitter }}" name="twitter:site">
{% endif %}
{% endblock %}
{% compress css %}
<link rel="stylesheet" type="text/css" href="{% static 'css/workout-manager.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/workout-manager-common.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/jquery-ui.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/jquery-ui-custom.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'bower_components/bootstrap/dist/css/bootstrap.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'bower_components/components-font-awesome/css/font-awesome.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap-custom.css' %}">
@@ -61,13 +61,14 @@
{% compress js %}
<script src="{% static 'bower_components/jquery/dist/jquery.js' %}"></script>
<script src="{% static 'js/jquery-ui.js' %}"></script>
<script src="{% static 'bower_components/d3/d3.js' %}"></script>
<script src="{% static 'js/wger-core.js' %}"></script>
<script src="{% static 'bower_components/bootstrap/dist/js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/Sortable.min.js' %}"></script>
<script src="{% static 'bower_components/d3/d3.js' %}"></script>
<script src="{% static 'bower_components/metrics-graphics/dist/metricsgraphics.min.js' %}"></script>
<script src="{% static 'bower_components/devbridge-autocomplete/dist/jquery.autocomplete.min.js' %}"></script>
<script src="{% static 'js/wger-core.js' %}"></script>
<script src="{% static 'js/exercises.js' %}"></script>
<script src="{% static 'js/nutrition.js' %}"></script>
<script src="{% static 'bower_components/metrics-graphics/dist/metricsgraphics.min.js' %}"></script>
{% endcompress %}
{% block header %}{% endblock %}

View File

@@ -38,8 +38,10 @@
<!-- twitter cards -->
{% block twittercard %}
{% if twitter %}
<meta content="summary" name="twitter:card">
<meta content="@wger_de" name="twitter:site">
<meta content="@{{ twitter }}" name="twitter:site">
{% endif %}
{% endblock %}

View File

@@ -441,7 +441,7 @@ class ExerciseImage(AbstractSubmissionModel, AbstractLicenseModel, models.Model)
if request.user.has_perm('exercises.add_exerciseimage'):
self.status = self.STATUS_ACCEPTED
if not self.license_author:
self.license_author = 'wger.de'
self.license_author = request.get_host().split(':')[0]
else:
if not self.license_author:

View File

@@ -15,15 +15,25 @@
<script>
$(function() {
// Init the autocompleter
$("#exercise-search").catcomplete({
source: "{% url 'exercise-search' %}",
minLength: 2,
select: function(event, ui) {
// Redirect to exercise after clicking on a result
// Path is hard coded here, because we can't use the url-tag
window.location.href = '/exercise/' + ui.item.id + '/view/'
}
$('#exercise-search').devbridgeAutocomplete({
serviceUrl: '{% url 'exercise-search' %}',
onSelect: function (suggestion) {
window.location.href = '/exercise/' + suggestion.data.id + '/view/'
},
groupBy: 'category',
paramName: 'term',
transformResult: function(response) {
// why is response not already a JSON object??
var jsonResponse = $.parseJSON(response);
return {
suggestions: $.map(jsonResponse, function(item) {
return {value: item.value, data: {id: item.id,
category: item.category,
image: item.image,
thumbnail: item.image_thumbnail}};
})
};
}
});
});
</script>
@@ -115,15 +125,10 @@ $(function() {
<h4>{% trans "Search" %}</h4>
<form action="{% url 'exercise-search' %}"
method="get"
id="exercise_search_form">
{% csrf_token %}
<input name="term"
type="search"
id="exercise-search"
class="ajax-form-element form-control"
placeholder="{% trans 'exercise name' %}"
style="width:100%;">
</form>
{% endblock %}

View File

@@ -16,20 +16,27 @@
<script>
$(document).ready(function() {
// Prevent the form from being submited if JavaScript is enabled
$("#exercise_search_form").submit(function() {return false;});
// Init the autocompleter
$("#exercise-search").catcomplete({
source: "{% url 'exercise-search' %}",
minLength: 2,
select: function(event, ui) {
// Redirect to exercise after clicking on a result
// Path is hard coded here, because we can't use the url-tag
window.location.href = '/exercise/' + ui.item.id + '/view/'
}
});
$('#exercise-search').devbridgeAutocomplete({
serviceUrl: '{% url 'exercise-search' %}',
onSelect: function (suggestion) {
window.location.href = '/exercise/' + suggestion.data.id + '/view/'
},
groupBy: 'category',
paramName: 'term',
transformResult: function(response) {
// why is response not already a JSON object??
var jsonResponse = $.parseJSON(response);
return {
suggestions: $.map(jsonResponse, function(item) {
return {value: item.value, data: {id: item.id,
category: item.category,
image: item.image,
thumbnail: item.image_thumbnail}};
})
};
}
});
});
</script>
{% endblock %}
@@ -124,16 +131,9 @@ $(document).ready(function() {
</p>
<h4>{% trans "Search" %}</h4>
<form action="{% url 'exercise-search' %}"
method="get"
id="exercise_search_form">
{% csrf_token %}
<input name="term"
type="search"
id="exercise-search"
class="ajax-form-element form-control"
placeholder="{% trans 'exercise name' %}"
>
</form>
placeholder="{% trans 'exercise name' %}">
{% endblock %}

View File

@@ -12,23 +12,29 @@
{% block header %}
<script>
$(document).ready(function() {
// Prevent the form from being submited if JavaScript is enabled
$("#exercise_search_form").submit(function() {return false;});
// Init the autocompleter
$("#exercise-search").catcomplete({
source: "{% url 'exercise-search' %}",
minLength: 2,
select: function(event, ui) {
// Redirect to exercise after clicking on a result
// Path is hard coded here, because we can't use the url-tag
window.location.href = '/exercise/' + ui.item.id + '/view/'
}
});
$(document).ready(function() {
// Init the autocompleter
$('#exercise-search').devbridgeAutocomplete({
serviceUrl: '{% url 'exercise-search' %}',
onSelect: function (suggestion) {
window.location.href = '/exercise/' + suggestion.data.id + '/view/'
},
groupBy: 'category',
paramName: 'term',
transformResult: function(response) {
// why is response not already a JSON object??
var jsonResponse = $.parseJSON(response);
return {
suggestions: $.map(jsonResponse, function(item) {
return {value: item.value, data: {id: item.id,
category: item.category,
image: item.image,
thumbnail: item.image_thumbnail}};
})
};
}
});
});
</script>
{% endblock %}
@@ -99,17 +105,11 @@
-->
{% block sidebar %}
<h4>{% trans "Search" %}</h4>
<form action="{% url 'exercise-search' %}"
method="get"
id="exercise_search_form">
{% csrf_token %}
<input name="term"
type="search"
id="exercise-search"
class="ajax-form-element form-control"
placeholder="{% trans 'exercise name' %}">
</form>
<p>

View File

@@ -5,7 +5,6 @@ from wger.settings_global import *
# Use 'DEBUG = True' to get more details for server errors
DEBUG = True
TEMPLATE_DEBUG = DEBUG
ADMINS = (
('Your name', 'your_email@example.com'),
@@ -59,3 +58,6 @@ if DEBUG:
# Sender address used for sent emails
WGER_SETTINGS['EMAIL_FROM'] = 'wger Workout Manager <wger@example.com>'
# Your twitter handle, if you have one for this instance.
#WGER_SETTINGS['TWITTER'] = ''

View File

@@ -99,7 +99,9 @@ BOWER_INSTALLED_APPS = (
'DataTables',
'components-font-awesome',
'tinymce',
'metrics-graphics'
'metrics-graphics',
'devbridge-autocomplete#1.2.x',
# 'sortablejs#1.4.x',
)
@@ -133,26 +135,40 @@ AUTHENTICATION_BACKENDS = (
'wger.utils.helpers.EmailAuthBackend'
)
# Set the context processors
TEMPLATE_CONTEXT_PROCESSORS = (
'wger.utils.context_processor.processor',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
# 'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'wger.utils.context_processor.processor',
# Django mobile
'django_mobile.context_processors.flavour',
# Django
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'django.contrib.messages.context_processors.messages',
# Breadcrumbs
'django.core.context_processors.request'
)
# Django mobile
'django_mobile.context_processors.flavour',
TEMPLATE_LOADERS = (
# Django mobile
'django_mobile.loader.Loader',
# Breadcrumbs
'django.core.context_processors.request'
],
'loaders': [
# Django mobile
'django_mobile.loader.Loader',
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
)
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
],
'debug': False
},
},
]
# Store the user messages in the session
MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'
@@ -350,5 +366,6 @@ WGER_SETTINGS = {
'USE_RECAPTCHA': False,
'REMOVE_WHITESPACE': False,
'ALLOW_REGISTRATION': True,
'EMAIL_FROM': 'wger Workout Manager <wger@example.com>'
'EMAIL_FROM': 'wger Workout Manager <wger@example.com>',
'TWITTER': False
}

View File

@@ -35,6 +35,9 @@ def processor(request):
# Application version
'version': get_version(),
# Twitter handle for this instance
'twitter': settings.WGER_SETTINGS['TWITTER'],
# User language
'language': language,

View File

@@ -24,7 +24,7 @@ from django.forms.widgets import (
SelectMultiple,
TextInput,
ChoiceFieldRenderer,
ChoiceInput)
CheckboxChoiceInput)
from django.forms import fields
@@ -144,9 +144,9 @@ class ExerciseAjaxSelect(SelectMultiple):
return ''
class CheckboxChoiceInputTranslated(ChoiceInput):
class CheckboxChoiceInputTranslated(CheckboxChoiceInput):
'''
Overwritten ChoiceInput
Overwritten CheckboxChoiceInput
This only translated the text for the select widgets
'''
@@ -158,9 +158,9 @@ class CheckboxChoiceInputTranslated(ChoiceInput):
super(CheckboxChoiceInputTranslated, self).__init__(name, value, attrs, choice, index)
class CheckboxChoiceInputTranslatedOriginal(ChoiceInput):
class CheckboxChoiceInputTranslatedOriginal(CheckboxChoiceInput):
'''
Overwritten ChoiceInput
Overwritten CheckboxChoiceInput
This only translated the text for the select widgets, showing the original
string as well.