diff --git a/extras/scripts/filter-fixtures.py b/extras/scripts/filter-fixtures.py index 641e6f1b5..3f5398004 100644 --- a/extras/scripts/filter-fixtures.py +++ b/extras/scripts/filter-fixtures.py @@ -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 diff --git a/wger/config/management/commands/extract-categories-i18n.py b/wger/config/management/commands/extract-categories-i18n.py index 8d0c86b89..2109bd601 100644 --- a/wger/config/management/commands/extract-categories-i18n.py +++ b/wger/config/management/commands/extract-categories-i18n.py @@ -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)) diff --git a/wger/exercises/api/resources.py b/wger/exercises/api/resources.py index 0e3c99030..22bba0bae 100644 --- a/wger/exercises/api/resources.py +++ b/wger/exercises/api/resources.py @@ -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: diff --git a/wger/exercises/fixtures/test-equipment.json b/wger/exercises/fixtures/test-equipment.json new file mode 100644 index 000000000..6b5a8f2b7 --- /dev/null +++ b/wger/exercises/fixtures/test-equipment.json @@ -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" + } + } +] diff --git a/wger/exercises/migrations/0009_auto__add_equipment.py b/wger/exercises/migrations/0009_auto__add_equipment.py new file mode 100644 index 000000000..a841f26d1 --- /dev/null +++ b/wger/exercises/migrations/0009_auto__add_equipment.py @@ -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'] \ No newline at end of file diff --git a/wger/exercises/models.py b/wger/exercises/models.py index 5a7675081..58307be4b 100644 --- a/wger/exercises/models.py +++ b/wger/exercises/models.py @@ -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''' diff --git a/wger/exercises/templates/equipment/list.html b/wger/exercises/templates/equipment/list.html new file mode 100644 index 000000000..bd083077d --- /dev/null +++ b/wger/exercises/templates/equipment/list.html @@ -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 %} + + + + + + + + + +{% for equipment in equipment_list %} + + + + +{% empty %} + + + +{% endfor %} + + +
{% trans "Actions" %}{% trans "Name" %}
+ + {% trans 'Delete' %} + + + {% trans 'Edit' %} + + {{equipment.name}}
{% trans "No equipment found" %}
+
+{% pagination paginator page_obj %} +{% endblock %} + + + + +{% block sidebar %} +{% if perms.exercises.add_equipment %} +

{% trans "Options" %}

+

+ + {% trans 'Add new equipment' %} + {% trans "Add new equipment" %} + +

+{% endif %} +{% endblock %} diff --git a/wger/exercises/templates/mobile/view.html b/wger/exercises/templates/mobile/view.html index b3b64d0dd..409e560c1 100644 --- a/wger/exercises/templates/mobile/view.html +++ b/wger/exercises/templates/mobile/view.html @@ -66,7 +66,11 @@ included in the database.{% endblocktrans %} {% endif %} {# end exercise is pending review #} -

{% trans "Category" %}: {% trans exercise.category.name %}

+

{% trans "Category" %}: {% trans exercise.category.name %}

+{% if exercise.equipment.all %} +

{% trans "Equipment" %}: {{ exercise.equipment.all|join:", " }}

+{% endif %} +
{{ exercise.description|safe }}
{# #} diff --git a/wger/exercises/templates/view.html b/wger/exercises/templates/view.html index 06429a22c..3a3cf17e1 100644 --- a/wger/exercises/templates/view.html +++ b/wger/exercises/templates/view.html @@ -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 %} -

{% trans "Category" %}: {% trans exercise.category.name %}

+

{% trans "Category" %}: {% trans exercise.category.name %}

+ +{% if exercise.equipment.all %} +

{% trans "Equipment" %}: {{ exercise.equipment.all|join:", " }}

+{% endif %} +
{{ exercise.description|safe }}
{% endcache %} @@ -87,7 +93,7 @@ included in the database.{% endblocktrans %} {# #} {% with comments=exercise.exercisecomment_set.all %} {% if comments %} -

{% trans "Comments for this exercise" %}

+

{% trans "Comments for this exercise" %}

diff --git a/wger/manager/templates/navigation.html b/wger/manager/templates/navigation.html index c914c37a1..93aef1eb1 100644 --- a/wger/manager/templates/navigation.html +++ b/wger/manager/templates/navigation.html @@ -51,6 +51,7 @@
  • {% trans "Exercises pending review" %}
  • +
  • {% trans "Equipment" %}
  • {% endif %} {% endcache %} diff --git a/wger/manager/tests/testcase.py b/wger/manager/tests/testcase.py index 80b047622..30914269c 100644 --- a/wger/manager/tests/testcase.py +++ b/wger/manager/tests/testcase.py @@ -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', diff --git a/wger/urls.py b/wger/urls.py index f9ac508fb..12f60016b 100644 --- a/wger/urls.py +++ b/wger/urls.py @@ -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()) diff --git a/wger/utils/tests/__init__.py b/wger/utils/tests/__init__.py index 361dfffc9..e69de29bb 100644 --- a/wger/utils/tests/__init__.py +++ b/wger/utils/tests/__init__.py @@ -1 +0,0 @@ -from wger.utils.tests.middleware import *