Add support for equipment needed for exercises

This commit is contained in:
Roland Geider
2013-11-02 11:01:31 +01:00
parent c47bef41e4
commit de5af37db8
20 changed files with 586 additions and 16 deletions

View File

@@ -52,6 +52,7 @@ filter_dump(data, ('exercises.muscle',), 'muscles.json')
filter_dump(data, ('exercises.exercisecategory',), 'categories.json')
filter_dump(data, ('exercises.exerciseimage',), 'exercise-images.json')
filter_dump(data, ('exercises.exercise', 'exercises.exercisecomment',), 'exercises.json')
filter_dump(data, ('exercises.equipment', 'exercises.equipment',), 'equipment.json')
#
# Other

View File

@@ -15,20 +15,24 @@
# You should have received a copy of the GNU Affero General Public License
from django.core.management.base import BaseCommand
from wger.exercises.models import ExerciseCategory
from wger.exercises.models import Equipment
class Command(BaseCommand):
'''
Helper command to read out the exercise categories to manually include in
the .po files
Helper command to read out the strings to manually include in the .po files
'''
help = 'Read the exercise categories to include in .po file'
help = 'Read out all strings that have to be included manually in the .po file'
def handle(self, *args, **options):
categories = ExerciseCategory.objects.all()
for category in categories:
self.stdout.write('msgid "{0}"\n'
'msgstr ""\n\n'.format(category))
for category in ExerciseCategory.objects.all():
self.stdout.write('msgid "{0}"\n'
'msgstr ""\n\n'.format(category))
for equipment in Equipment.objects.all():
self.stdout.write('msgid "{0}"\n'
'msgstr ""\n\n'.format(equipment))

View File

@@ -26,6 +26,7 @@ from wger.exercises.models import ExerciseComment
from wger.exercises.models import ExerciseImage
from wger.exercises.models import Muscle
from wger.exercises.models import Language
from wger.exercises.models import Equipment
class ExerciseResource(ModelResource):
@@ -43,6 +44,12 @@ class ExerciseResource(ModelResource):
queryset = Exercise.objects.all()
class EquipmentResource(ModelResource):
class Meta:
queryset = Equipment.objects.all()
class ExerciseCategoryResource(ModelResource):
class Meta:

View File

@@ -0,0 +1,23 @@
[
{
"pk": 1,
"model": "exercises.equipment",
"fields": {
"name": "Dumbbells"
}
},
{
"pk": 2,
"model": "exercises.equipment",
"fields": {
"name": "Kettlebell"
}
},
{
"pk": 3,
"model": "exercises.equipment",
"fields": {
"name": "Something else"
}
}
]

View File

@@ -0,0 +1,124 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Equipment'
db.create_table(u'exercises_equipment', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=50)),
))
db.send_create_signal(u'exercises', ['Equipment'])
# Adding M2M table for field equipment on 'Exercise'
m2m_table_name = db.shorten_name(u'exercises_exercise_equipment')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('exercise', models.ForeignKey(orm[u'exercises.exercise'], null=False)),
('equipment', models.ForeignKey(orm[u'exercises.equipment'], null=False))
))
db.create_unique(m2m_table_name, ['exercise_id', 'equipment_id'])
def backwards(self, orm):
# Deleting model 'Equipment'
db.delete_table(u'exercises_equipment')
# Removing M2M table for field equipment on 'Exercise'
db.delete_table(db.shorten_name(u'exercises_exercise_equipment'))
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'exercises.equipment': {
'Meta': {'ordering': "['name']", 'object_name': 'Equipment'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'exercises.exercise': {
'Meta': {'ordering': "['name']", 'object_name': 'Exercise'},
'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['exercises.ExerciseCategory']"}),
'creation_date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'max_length': '2000'}),
'equipment': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['exercises.Equipment']", 'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['exercises.Language']"}),
'muscles': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['exercises.Muscle']", 'symmetrical': 'False'}),
'muscles_secondary': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'secondary_muscles'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['exercises.Muscle']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'1'", 'max_length': '2'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
},
u'exercises.exercisecategory': {
'Meta': {'ordering': "['name']", 'object_name': 'ExerciseCategory'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
u'exercises.exercisecomment': {
'Meta': {'object_name': 'ExerciseComment'},
'comment': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'exercise': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['exercises.Exercise']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
u'exercises.exerciseimage': {
'Meta': {'ordering': "['-is_main', 'id']", 'object_name': 'ExerciseImage'},
'exercise': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['exercises.Exercise']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '100'}),
'is_main': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
},
u'exercises.language': {
'Meta': {'object_name': 'Language'},
'full_name': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'short_name': ('django.db.models.fields.CharField', [], {'max_length': '2'})
},
u'exercises.muscle': {
'Meta': {'ordering': "['name']", 'object_name': 'Muscle'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_front': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
}
}
complete_apps = ['exercises']

View File

@@ -81,9 +81,9 @@ class Muscle(models.Model):
Muscle an exercise works out
'''
# Name, in latin, e.g. "Pectoralis major"
name = models.CharField(max_length=50,
verbose_name=_('Name'))
verbose_name=_('Name'),
help_text=_('In latin, e.g. "Pectoralis major"'))
# Whether to use the front or the back image for background
is_front = models.BooleanField(default=1)
@@ -105,6 +105,33 @@ class Muscle(models.Model):
return False
class Equipment(models.Model):
'''
Equipment used or needed by an exercise
'''
name = models.CharField(max_length=50,
verbose_name=_('Name'))
class Meta:
'''
Set default ordering
'''
ordering = ["name", ]
def __unicode__(self):
'''
Return a more human-readable representation
'''
return self.name
def get_owner_object(self):
'''
Equipment has no owner information
'''
return False
class ExerciseCategory(models.Model):
'''
Model for an exercise category
@@ -175,6 +202,12 @@ class Exercise(models.Model):
)
'''Secondary muscles trained by the exercise'''
equipment = models.ManyToManyField(Equipment,
verbose_name=_('Equipment'),
null=True,
blank=True)
'''Equipment needed by this exercise'''
# Non-editable fields
user = models.ForeignKey(User, verbose_name=_('User'), null=True, blank=True)
'''The user that submitted the exercise'''

View File

@@ -0,0 +1,72 @@
{% extends "base.html" %}
{% load url from future %}
{% load i18n %}
{% load staticfiles %}
{% load wger_extras %}
{% block title %}{% trans "Equipment list" %}{% endblock %}
{% block content %}
<table>
<thead>
<tr>
<th>{% trans "Actions" %}</th>
<th>{% trans "Name" %}</th>
</tr>
</thead>
<tbody>
{% for equipment in equipment_list %}
<tr>
<td class="no-hover">
<a href="{% url 'equipment-delete' equipment.id %}"
class="modal-dialog">
<img src="{% static 'images/icons/trash.svg' %}"
width="22"
height="22"
alt="{% trans 'Delete' %}"
title="{% trans 'Delete' %}">
</a>
<a href="{% url 'equipment-edit' equipment.id %}"
class="modal-dialog">
<img src="{% static 'images/icons/edit.svg' %}"
width="22"
height="22"
alt="{% trans 'Edit' %}"
title="{% trans 'Edit' %}">
</a>
</td>
<td>{{equipment.name}}</td>
</tr>
{% empty %}
<tr>
<td colspan="2">{% trans "No equipment found" %}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div style="padding-top:3em;"></div>
{% pagination paginator page_obj %}
{% endblock %}
{% block sidebar %}
{% if perms.exercises.add_equipment %}
<h3>{% trans "Options" %}</h3>
<p>
<a href="{% url 'equipment-add' %}"
class="modal-dialog">
<img src="{% static 'images/icons/new.svg' %}"
width="32"
height="32"
alt="{% trans 'Add new equipment' %}"
title="{% trans 'Add new equipment' %}">
{% trans "Add new equipment" %}
</a>
</p>
{% endif %}
{% endblock %}

View File

@@ -66,7 +66,11 @@ included in the database.{% endblocktrans %}
{% endif %} {# end exercise is pending review #}
<p>{% trans "Category" %}: <strong>{% trans exercise.category.name %}</strong></p>
<p><strong>{% trans "Category" %}:</strong> {% trans exercise.category.name %}</p>
{% if exercise.equipment.all %}
<p><strong>{% trans "Equipment" %}:</strong> {{ exercise.equipment.all|join:", " }}</p>
{% endif %}
<div>{{ exercise.description|safe }}</div>
{# #}

View File

@@ -76,8 +76,14 @@ included in the database.{% endblocktrans %}
{% endif %} {# end exercise is pending review #}
{% cache cache_timeout exercise-detail-header exercise.id language.id %}
<p>{% trans "Category" %}: <strong>{% trans exercise.category.name %}</strong></p>
<p><strong>{% trans "Category" %}:</strong> {% trans exercise.category.name %}</p>
{% if exercise.equipment.all %}
<p><strong>{% trans "Equipment" %}:</strong> {{ exercise.equipment.all|join:", " }}</p>
{% endif %}
<div>{{ exercise.description|safe }}</div>
{% endcache %}
@@ -87,7 +93,7 @@ included in the database.{% endblocktrans %}
{# #}
{% with comments=exercise.exercisecomment_set.all %}
{% if comments %}
<h4>{% trans "Comments for this exercise" %}</h4>
<h3>{% trans "Comments for this exercise" %}</h3>
<ul>
{% for comment in comments %}
@@ -137,7 +143,7 @@ included in the database.{% endblocktrans %}
{# #}
{% with images=exercise.exerciseimage_set.all %}
{% if images %}
<h4>{% trans "Images" %}</h4>
<h3>{% trans "Images" %}</h3>
{% with main_image=images|first %}

View File

@@ -0,0 +1,115 @@
# This file is part of wger Workout Manager.
#
# wger 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.
#
# wger 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 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/>.
from django.core.urlresolvers import reverse
from wger.exercises.models import Equipment
from wger.manager.tests.testcase import WorkoutManagerTestCase
from wger.manager.tests.testcase import WorkoutManagerDeleteTestCase
from wger.manager.tests.testcase import WorkoutManagerEditTestCase
from wger.manager.tests.testcase import WorkoutManagerAddTestCase
from wger.manager.tests.testcase import ApiBaseResourceTestCase
from wger.utils.constants import PAGINATION_OBJECTS_PER_PAGE
class AddEquipmentTestCase(WorkoutManagerAddTestCase):
'''
Tests adding a new equipment
'''
object_class = Equipment
url = 'equipment-add'
data = {'name': 'A new equipment'}
pk = 4
class DeleteEquipmentTestCase(WorkoutManagerDeleteTestCase):
'''
Tests deleting an equipment
'''
object_class = Equipment
url = 'equipment-delete'
pk = 1
class EditEquipmentTestCase(WorkoutManagerEditTestCase):
'''
Tests editing an equipment
'''
object_class = Equipment
url = 'equipment-edit'
pk = 1
data = {'name': 'A new name'}
class EquipmentOverviewTestCase(WorkoutManagerTestCase):
'''
Tests the equipment overview page
'''
def test_overview(self):
# Add more equipments so we can test the pagination
self.user_login('admin')
data = {"name": "A new entry"}
for i in range(0, 50):
self.client.post(reverse('equipment-add'), data)
# Page exists and the pagination works
response = self.client.get(reverse('equipment-list'))
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.context['equipment_list']), PAGINATION_OBJECTS_PER_PAGE)
response = self.client.get(reverse('equipment-list'), {'page': 2})
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.context['equipment_list']), PAGINATION_OBJECTS_PER_PAGE)
response = self.client.get(reverse('equipment-list'), {'page': 3})
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.context['equipment_list']), 3)
# 'last' is a special case
response = self.client.get(reverse('equipment-list'), {'page': 'last'})
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.context['equipment_list']), 3)
# Page does not exist
response = self.client.get(reverse('equipment-list'), {'page': 100})
self.assertEqual(response.status_code, 404)
response = self.client.get(reverse('equipment-list'), {'page': 'foobar'})
self.assertEqual(response.status_code, 404)
class EquipmentApiTestCase(ApiBaseResourceTestCase):
'''
Tests the equipment overview resource
'''
resource = 'equipment'
user = None
resource_updatable = False
class EquipmentDetailApiTestCase(ApiBaseResourceTestCase):
'''
Tests accessing a specific equipment
'''
resource = 'equipment/1'
user = None
resource_updatable = False

View File

@@ -6,6 +6,7 @@ from wger.exercises.views import comments
from wger.exercises.views import categories
from wger.exercises.views import muscles
from wger.exercises.views import images
from wger.exercises.views import equipment
urlpatterns = patterns('wger.exercises.views',
@@ -87,4 +88,18 @@ urlpatterns = patterns('wger.exercises.views',
images.ExerciseImageDeleteView.as_view(),
name='exerciseimage-delete'),
# Equipment
url(r'^equipment/list$',
equipment.EquipmentListView.as_view(),
name='equipment-list'),
url(r'^equipment/add$',
equipment.EquipmentAddView.as_view(),
name='equipment-add'),
url(r'^equipment/(?P<pk>\d+)/edit$',
equipment.EquipmentEditView.as_view(),
name='equipment-edit'),
url(r'^equipment/(?P<pk>\d+)/delete$',
equipment.EquipmentDeleteView.as_view(),
name='equipment-delete'),
)

View File

@@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
# This file is part of wger Workout Manager.
#
# wger 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.
#
# wger 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 General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
import logging
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy
from django.utils.translation import ugettext as _
from django.views.generic import CreateView
from django.views.generic import UpdateView
from django.views.generic import DeleteView
from django.views.generic import ListView
from wger.exercises.models import Equipment
from wger.utils.generic_views import WgerFormMixin
from wger.utils.generic_views import WgerDeleteMixin
from wger.utils.generic_views import WgerPermissionMixin
from wger.utils.constants import PAGINATION_OBJECTS_PER_PAGE
logger = logging.getLogger('wger.custom')
'''
Exercise equipment
'''
class EquipmentListView(WgerPermissionMixin, ListView):
'''
Generic view to list all equipments
'''
model = Equipment
template_name = 'equipment/list.html'
context_object_name = 'equipment_list'
paginate_by = PAGINATION_OBJECTS_PER_PAGE
permission_required = 'exercises.change_equipment'
class EquipmentEditView(WgerFormMixin, UpdateView, WgerPermissionMixin):
'''
Generic view to update an existing equipment item
'''
model = Equipment
title = ugettext_lazy('Edit equipment')
permission_required = 'exercises.change_equipment'
success_url = reverse_lazy('equipment-list')
# Send some additional data to the template
def get_context_data(self, **kwargs):
context = super(EquipmentEditView, self).get_context_data(**kwargs)
context['form_action'] = reverse('equipment-edit',
kwargs={'pk': self.object.id})
return context
class EquipmentAddView(WgerFormMixin, CreateView, WgerPermissionMixin):
'''
Generic view to add a new equipment item
'''
model = Equipment
title = ugettext_lazy('Add equipment')
permission_required = 'exercises.add_equipment'
success_url = reverse_lazy('equipment-list')
def get_context_data(self, **kwargs):
'''
Send some additional data to the template
'''
context = super(EquipmentAddView, self).get_context_data(**kwargs)
context['form_action'] = reverse('equipment-add')
return context
class EquipmentDeleteView(WgerDeleteMixin, DeleteView, WgerPermissionMixin):
'''
Generic view to delete an existing exercise image
'''
model = Equipment
messages = ugettext_lazy('Equipment successfully deleted')
permission_required = 'exercises.delete_equipment'
success_url = reverse_lazy('equipment-list')
def get_context_data(self, **kwargs):
'''
Send some additional data to the template
'''
pk = self.kwargs['pk']
context = super(EquipmentDeleteView, self).get_context_data(**kwargs)
context['title'] = _('Delete equipment?')
context['form_action'] = reverse('equipment-delete',
kwargs={'pk': pk})
return context

View File

@@ -55,6 +55,7 @@ from wger.utils.language import load_language
from wger.utils.language import load_item_languages
from wger.utils.cache import cache_mapper
from wger.utils.widgets import TranslatedSelect
from wger.utils.widgets import TranslatedSelectMultiple
from wger.config.models import LanguageConfig
@@ -159,7 +160,8 @@ class ExercisesEditAddView(WgerFormMixin):
'category',
'muscles',
'muscles_secondary',
'description']
'description',
'equipment']
title = ugettext_lazy('Add exercise')
custom_js = 'init_tinymce();'
@@ -177,6 +179,7 @@ class ExercisesEditAddView(WgerFormMixin):
class Meta:
model = Exercise
widgets = {'equipment': TranslatedSelectMultiple()}
class Media:
js = ('js/tinymce/tiny_mce.js',)

View File

@@ -20,6 +20,21 @@
"config",
"languageconfig"
],
[
"add_equipment",
"exercises",
"equipment"
],
[
"change_equipment",
"exercises",
"equipment"
],
[
"delete_equipment",
"exercises",
"equipment"
],
[
"add_exercise",
"exercises",
@@ -158,4 +173,4 @@
]
}
}
]
]

View File

@@ -20,6 +20,21 @@
"config",
"languageconfig"
],
[
"add_equipment",
"exercises",
"equipment"
],
[
"change_equipment",
"exercises",
"equipment"
],
[
"delete_equipment",
"exercises",
"equipment"
],
[
"add_exercise",
"exercises",
@@ -65,6 +80,21 @@
"exercises",
"exercisecomment"
],
[
"add_exerciseimage",
"exercises",
"exerciseimage"
],
[
"change_exerciseimage",
"exercises",
"exerciseimage"
],
[
"delete_exerciseimage",
"exercises",
"exerciseimage"
],
[
"add_language",

View File

@@ -62,6 +62,7 @@
{% if perms.exercises %}
<li data-role="list-divider">{% trans "Administration" %}</li>
<li><a href="{% url 'exercise-pending' %}">{% trans "Exercises pending review" %}</a></li>
<li><a href="{% url 'equipment-list' %}">{% trans "Equipment" %}</a></li>
{% endif %}
</ul>
</div>

View File

@@ -51,6 +51,7 @@
<li class="divider"></li>
<li class="nav-header">{% trans "Administration" %}</li>
<li><a href="{% url 'exercise-pending' %}">{% trans "Exercises pending review" %}</a></li>
<li><a href="{% url 'equipment-list' %}">{% trans "Equipment" %}</a></li>
{% endif %}
{% endcache %}
</ul>

View File

@@ -58,6 +58,7 @@ class BaseTestCase(object):
'test-user-data',
'test-apikeys',
'test-weight-data',
'test-equipment',
'test-exercises',
'test-exercise-images',
'test-weight-units',

View File

@@ -25,6 +25,7 @@ v1_api.register(exercises_api.ExerciseImageResource())
v1_api.register(exercises_api.ExerciseResource())
v1_api.register(exercises_api.MuscleResource())
v1_api.register(exercises_api.LanguageResource())
v1_api.register(exercises_api.EquipmentResource())
# Nutrition app
v1_api.register(nutrition_api.IngredientResource())

View File

@@ -1 +0,0 @@
from wger.utils.tests.middleware import *