mass refactor
This commit is contained in:
parent
b61d681685
commit
c16b4caaa1
3
fitnotes/admin.py
Normal file
3
fitnotes/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
40
fitnotes/importer.py
Normal file
40
fitnotes/importer.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import sqlite3
|
||||||
|
from django.db import transaction
|
||||||
|
from lifting.models import Lift, Set
|
||||||
|
|
||||||
|
|
||||||
|
def _clean_name(name):
|
||||||
|
return name.lower()
|
||||||
|
|
||||||
|
|
||||||
|
def import_fitnotes_db(filename, user, fitnotes_to_lift):
|
||||||
|
# lift name => id
|
||||||
|
lift_ids = {_clean_name(l.name): l.id for l in Lift.objects.all()}
|
||||||
|
|
||||||
|
# build mapping FitNotes exercise id => our lift id
|
||||||
|
lift_id_mapping = {}
|
||||||
|
|
||||||
|
conn = sqlite3.connect(filename)
|
||||||
|
cur = conn.cursor()
|
||||||
|
for fnid, ename in cur.execute('SELECT _id, name FROM exercise WHERE exercise_type_id=0'):
|
||||||
|
cleaned = _clean_name(ename)
|
||||||
|
|
||||||
|
if cleaned not in fitnotes_to_lift:
|
||||||
|
lift_id_mapping[fnid] = cleaned
|
||||||
|
else:
|
||||||
|
lift_id_mapping[fnid] = lift_ids[fitnotes_to_lift[cleaned]]
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
Set.objects.filter(source='fitnotes').delete()
|
||||||
|
for fnid, date, weight_kg, reps in cur.execute(
|
||||||
|
'SELECT exercise_id, date, metric_weight, reps FROM training_log'):
|
||||||
|
|
||||||
|
# error if mapping wasn't found and there's a workout using it
|
||||||
|
if isinstance(lift_id_mapping[fnid], str):
|
||||||
|
raise ValueError('no known conversion for fitnotes exercise "{}"'.format(
|
||||||
|
lift_id_mapping[fnid]))
|
||||||
|
|
||||||
|
lift_id = lift_id_mapping[fnid]
|
||||||
|
|
||||||
|
Set.objects.create(lift_id=lift_id, date=date, weight_kg=weight_kg, reps=reps,
|
||||||
|
source='fitnotes', user=user)
|
0
fitnotes/migrations/__init__.py
Normal file
0
fitnotes/migrations/__init__.py
Normal file
3
fitnotes/models.py
Normal file
3
fitnotes/models.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
62
fitnotes/tests.py
Normal file
62
fitnotes/tests.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from inventory.models import Lift
|
||||||
|
from lifting.models import Set
|
||||||
|
from .importer import import_fitnotes_db
|
||||||
|
|
||||||
|
|
||||||
|
class TestFitnotesImport(TestCase):
|
||||||
|
# fitnotes.db has:
|
||||||
|
# April 1
|
||||||
|
# bench press 10 @ 45
|
||||||
|
# bench press 5 @ 95
|
||||||
|
# bench press 3 @ 135
|
||||||
|
# bench press 5 @ 155
|
||||||
|
# April 3
|
||||||
|
# squat 10 @ 45
|
||||||
|
# squat 5 @ 95
|
||||||
|
# squat 3 @ 135
|
||||||
|
# squat 2 @ 185
|
||||||
|
# squat 5 @ 225
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.user = User.objects.create_user('default', 'default@example.com', 'default')
|
||||||
|
self.bench = Lift.objects.create(name='bench press')
|
||||||
|
self.squat = Lift.objects.create(name='squat')
|
||||||
|
|
||||||
|
self.good_mapping = {'flat barbell bench press': 'bench press',
|
||||||
|
'barbell squat': 'squat'
|
||||||
|
}
|
||||||
|
self.bad_mapping = {'flat barbell bench press': 'bench press' }
|
||||||
|
|
||||||
|
def test_basic_import(self):
|
||||||
|
# ensure that the data comes in
|
||||||
|
import_fitnotes_db('fitnotes/testdata/example.fitnotes', self.user, self.good_mapping)
|
||||||
|
assert Set.objects.filter(lift=self.bench).count() == 4
|
||||||
|
assert Set.objects.filter(lift=self.squat).count() == 5
|
||||||
|
|
||||||
|
def test_double_import(self):
|
||||||
|
# two identical dbs, should be idempotent
|
||||||
|
import_fitnotes_db('fitnotes/testdata/example.fitnotes', self.user, self.good_mapping)
|
||||||
|
import_fitnotes_db('fitnotes/testdata/example.fitnotes', self.user, self.good_mapping)
|
||||||
|
assert Set.objects.filter(lift=self.bench).count() == 4
|
||||||
|
assert Set.objects.filter(lift=self.squat).count() == 5
|
||||||
|
|
||||||
|
def test_import_with_other_data(self):
|
||||||
|
Set.objects.create(lift=self.bench, weight_kg=100, reps=10, date='2014-01-01',
|
||||||
|
user=self.user)
|
||||||
|
import_fitnotes_db('fitnotes/testdata/example.fitnotes', self.user, self.good_mapping)
|
||||||
|
assert Set.objects.filter(lift=self.bench).count() == 5
|
||||||
|
|
||||||
|
def test_import_with_bad_mapping(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
import_fitnotes_db('fitnotes/testdata/example.fitnotes', self.user, self.bad_mapping)
|
||||||
|
assert Set.objects.filter(lift=self.bench).count() == 0
|
||||||
|
|
||||||
|
def test_bad_data_import(self):
|
||||||
|
# good db then bad db, should fail without screwing up existing data
|
||||||
|
import_fitnotes_db('fitnotes/testdata/example.fitnotes', self.user, self.good_mapping)
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
# baddata.fitnotes has all lift ids set to 9999
|
||||||
|
import_fitnotes_db('fitnotes/testdata/baddata.fitnotes', self.user, self.good_mapping)
|
||||||
|
assert Set.objects.count() == 9
|
@ -3,5 +3,5 @@ from django.conf.urls import url
|
|||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^edit/$', views.edit_profile, name='edit-profile'),
|
url(r'^fitnotes/$', views.fitnotes_upload),
|
||||||
]
|
]
|
18
fitnotes/views.py
Normal file
18
fitnotes/views.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def fitnotes_upload(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = FitnotesUploadForm(request.POST, request.FILES)
|
||||||
|
if form.is_valid():
|
||||||
|
_, fname = tempfile.mkstemp()
|
||||||
|
with open(fname, 'wb') as tmp:
|
||||||
|
for chunk in request.FILES['file'].chunks():
|
||||||
|
tmp.write(chunk)
|
||||||
|
try:
|
||||||
|
importers.import_fitnotes_db(fname, request.user)
|
||||||
|
finally:
|
||||||
|
os.remove(fname)
|
||||||
|
else:
|
||||||
|
form = FitnotesUploadForm()
|
||||||
|
return render(request, 'lifting/fitnotes.html', {'form': form})
|
0
inventory/__init__.py
Normal file
0
inventory/__init__.py
Normal file
6
inventory/admin.py
Normal file
6
inventory/admin.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from .models import Lift
|
||||||
|
|
||||||
|
@admin.register(Lift)
|
||||||
|
class LiftAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
28
inventory/migrations/0001_initial.py
Normal file
28
inventory/migrations/0001_initial.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Bar',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)),
|
||||||
|
('name', models.CharField(max_length=100)),
|
||||||
|
('weight_kg', models.DecimalField(max_digits=7, decimal_places=3)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Lift',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)),
|
||||||
|
('name', models.CharField(max_length=200)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
0
inventory/migrations/__init__.py
Normal file
0
inventory/migrations/__init__.py
Normal file
22
inventory/models.py
Normal file
22
inventory/models.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from django.db import models
|
||||||
|
from common import to_lb
|
||||||
|
|
||||||
|
|
||||||
|
class Bar(models.Model):
|
||||||
|
name = models.CharField(max_length=100)
|
||||||
|
weight_kg = models.DecimalField(max_digits=7, decimal_places=3)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '{} ({}lb / {}kg)'.format(self.name, self.weight_kg, self.weight_lb)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def weight_lb(self):
|
||||||
|
return to_lb(self.weight_kg)
|
||||||
|
|
||||||
|
|
||||||
|
class Lift(models.Model):
|
||||||
|
name = models.CharField(max_length=200)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
3
inventory/tests.py
Normal file
3
inventory/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
@ -0,0 +1 @@
|
|||||||
|
default_app_config = 'lifting.apps.LiftingConfig'
|
@ -1,15 +1,10 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import Exercise, Set
|
from .models import Set
|
||||||
|
|
||||||
@admin.register(Exercise)
|
|
||||||
class ExerciseAdmin(admin.ModelAdmin):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Set)
|
@admin.register(Set)
|
||||||
class SetAdmin(admin.ModelAdmin):
|
class SetAdmin(admin.ModelAdmin):
|
||||||
date_hierarchy = 'date'
|
date_hierarchy = 'date'
|
||||||
readonly_fields = ('user', 'exercise', 'date')
|
readonly_fields = ('user', 'lift', 'date')
|
||||||
list_filter = ('user__username', 'exercise')
|
list_filter = ('user__username', 'lift')
|
||||||
fields = ('user', 'exercise', 'date', 'weight_kg', 'reps', 'source')
|
fields = ('user', 'lift', 'date', 'weight_kg', 'reps', 'source')
|
||||||
|
|
||||||
|
@ -5,14 +5,14 @@ from django.contrib.auth.models import User
|
|||||||
|
|
||||||
|
|
||||||
def create_profile(sender, created, instance, **kwargs):
|
def create_profile(sender, created, instance, **kwargs):
|
||||||
from .models import Profile
|
from .models import LiftingOptions
|
||||||
if created:
|
if created:
|
||||||
Profile.objects.create(user=instance)
|
LiftingOptions.objects.create(user=instance)
|
||||||
|
|
||||||
|
|
||||||
class ProfileConfig(AppConfig):
|
class LiftingConfig(AppConfig):
|
||||||
name = 'profiles'
|
name = 'lifting'
|
||||||
app_label = 'profiles'
|
app_label = 'lifting'
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
post_save.connect(create_profile, sender=User)
|
post_save.connect(create_profile, sender=User)
|
@ -1,40 +0,0 @@
|
|||||||
import sqlite3
|
|
||||||
from django.db import transaction
|
|
||||||
from lifting.models import Exercise, Set
|
|
||||||
|
|
||||||
|
|
||||||
def _clean_name(name):
|
|
||||||
return name.lower()
|
|
||||||
|
|
||||||
|
|
||||||
def import_fitnotes_db(filename, user):
|
|
||||||
# exercise names to db ids
|
|
||||||
exercises = {}
|
|
||||||
for e in Exercise.objects.all():
|
|
||||||
for n in e.names:
|
|
||||||
exercises[_clean_name(n)] = e.id
|
|
||||||
|
|
||||||
# build mapping FitNotes exercise id => our exercise id
|
|
||||||
exercise_id_mapping = {}
|
|
||||||
|
|
||||||
conn = sqlite3.connect(filename)
|
|
||||||
cur = conn.cursor()
|
|
||||||
for fnid, ename in cur.execute('SELECT _id, name FROM exercise WHERE exercise_type_id=0'):
|
|
||||||
cleaned = _clean_name(ename)
|
|
||||||
# map to an Exercise id or str
|
|
||||||
exercise_id_mapping[fnid] = exercises[cleaned] if cleaned in exercises else cleaned
|
|
||||||
|
|
||||||
with transaction.atomic():
|
|
||||||
Set.objects.filter(source='fitnotes').delete()
|
|
||||||
for fnid, date, weight_kg, reps in cur.execute(
|
|
||||||
'SELECT exercise_id, date, metric_weight, reps FROM training_log'):
|
|
||||||
|
|
||||||
# create Exercise if it wasn't found and there's a workout using it
|
|
||||||
if isinstance(exercise_id_mapping[fnid], str):
|
|
||||||
exercise_id_mapping[fnid] = Exercise.objects.create(
|
|
||||||
names=[exercise_id_mapping[fnid]]).id
|
|
||||||
|
|
||||||
exercise_id = exercise_id_mapping[fnid]
|
|
||||||
|
|
||||||
Set.objects.create(exercise_id=exercise_id, date=date, weight_kg=weight_kg, reps=reps,
|
|
||||||
source='fitnotes', user=user)
|
|
@ -3,30 +3,36 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.db import models, migrations
|
from django.db import models, migrations
|
||||||
import django.contrib.postgres.fields
|
import django.contrib.postgres.fields
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('inventory', '0001_initial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Exercise',
|
name='LiftingOptions',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', auto_created=True, primary_key=True, serialize=False)),
|
('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)),
|
||||||
('names', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), size=None)),
|
('lifting_units', models.CharField(default='i', choices=[('m', 'Metric (kg)'), ('i', 'Imperial (lb)')], max_length=1)),
|
||||||
|
('plate_pairs', django.contrib.postgres.fields.ArrayField(base_field=models.DecimalField(max_digits=7, decimal_places=3), default=['45', '45', '25', '10', '5', '5', '2.5', '1.25'], size=None)),
|
||||||
|
('user', models.OneToOneField(related_name='lifting_options', to=settings.AUTH_USER_MODEL)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Set',
|
name='Set',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', auto_created=True, primary_key=True, serialize=False)),
|
('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)),
|
||||||
('date', models.DateField()),
|
('date', models.DateField()),
|
||||||
('weight_kg', models.DecimalField(decimal_places=3, max_digits=7)),
|
('weight_kg', models.DecimalField(max_digits=7, decimal_places=3)),
|
||||||
('reps', models.PositiveIntegerField()),
|
('reps', models.PositiveIntegerField()),
|
||||||
('source', models.CharField(max_length=100)),
|
('source', models.CharField(max_length=100)),
|
||||||
('exercise', models.ForeignKey(to='lifting.Exercise', related_name='sets')),
|
('lift', models.ForeignKey(related_name='sets', to='inventory.Lift')),
|
||||||
|
('user', models.ForeignKey(related_name='sets', to=settings.AUTH_USER_MODEL)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
('lifting', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='set',
|
|
||||||
name='user',
|
|
||||||
field=models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='sets', default=None),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,30 +1,34 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
|
from inventory.models import Lift
|
||||||
|
|
||||||
SET_TYPES = (
|
SET_TYPES = (
|
||||||
('warmup', 'Warmup'),
|
('warmup', 'Warmup'),
|
||||||
('planned', 'Planned'),
|
('planned', 'Planned'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
UNITS = (
|
||||||
|
('m', 'Metric (kg)'),
|
||||||
|
('i', 'Imperial (lb)'),
|
||||||
|
)
|
||||||
|
|
||||||
class Exercise(models.Model):
|
|
||||||
names = ArrayField(models.CharField(max_length=200))
|
|
||||||
|
|
||||||
def display_name(self):
|
class LiftingOptions(models.Model):
|
||||||
return self.names[0].title()
|
user = models.OneToOneField(User, related_name='lifting_options')
|
||||||
|
|
||||||
def __str__(self):
|
lifting_units = models.CharField(max_length=1, choices=UNITS, default='i')
|
||||||
return ', '.join(self.names)
|
plate_pairs = ArrayField(models.DecimalField(max_digits=7, decimal_places=3),
|
||||||
|
default=['45','45','25','10','5','5','2.5','1.25'])
|
||||||
|
|
||||||
|
|
||||||
class Set(models.Model):
|
class Set(models.Model):
|
||||||
user = models.ForeignKey(User, related_name='sets')
|
user = models.ForeignKey(User, related_name='sets')
|
||||||
date = models.DateField()
|
date = models.DateField()
|
||||||
exercise = models.ForeignKey(Exercise, related_name='sets')
|
lift = models.ForeignKey(Lift, related_name='sets')
|
||||||
weight_kg = models.DecimalField(max_digits=7, decimal_places=3)
|
weight_kg = models.DecimalField(max_digits=7, decimal_places=3)
|
||||||
reps = models.PositiveIntegerField()
|
reps = models.PositiveIntegerField()
|
||||||
source = models.CharField(max_length=100)
|
source = models.CharField(max_length=100)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{} - {} @ {}kg - {}'.format(self.exercise, self.reps, self.weight_kg, self.date)
|
return '{} - {} @ {}kg - {}'.format(self.lift, self.reps, self.weight_kg, self.date)
|
||||||
|
@ -1,55 +1,10 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from lifting.models import Exercise, Set
|
from lifting.models import LiftingOptions
|
||||||
from lifting.importers import import_fitnotes_db
|
|
||||||
|
|
||||||
|
|
||||||
class TestFitnotesImport(TestCase):
|
class TestLiftingOptions(TestCase):
|
||||||
# fitnotes.db has:
|
|
||||||
# April 1
|
|
||||||
# bench press 10 @ 45
|
|
||||||
# bench press 5 @ 95
|
|
||||||
# bench press 3 @ 135
|
|
||||||
# bench press 5 @ 155
|
|
||||||
# April 3
|
|
||||||
# squat 10 @ 45
|
|
||||||
# squat 5 @ 95
|
|
||||||
# squat 3 @ 135
|
|
||||||
# squat 2 @ 185
|
|
||||||
# squat 5 @ 225
|
|
||||||
|
|
||||||
def setUp(self):
|
def test_signal(self):
|
||||||
self.user = User.objects.create_user('default', 'default@example.com', 'default')
|
u = User.objects.create_user(username='test', email='test@example.com', password='test')
|
||||||
|
assert LiftingOptions.objects.filter(user=u).count() == 1
|
||||||
def test_basic_import(self):
|
|
||||||
# ensure that the data comes in
|
|
||||||
import_fitnotes_db('lifting/testdata/example.fitnotes', self.user)
|
|
||||||
|
|
||||||
assert Exercise.objects.count() == 2
|
|
||||||
bp = Exercise.objects.get(names__contains=["flat barbell bench press"])
|
|
||||||
squat = Exercise.objects.get(names__contains=["barbell squat"])
|
|
||||||
assert Set.objects.count() == 9
|
|
||||||
|
|
||||||
def test_double_import(self):
|
|
||||||
# two identical dbs, should be idempotent
|
|
||||||
import_fitnotes_db('lifting/testdata/example.fitnotes', self.user)
|
|
||||||
import_fitnotes_db('lifting/testdata/example.fitnotes', self.user)
|
|
||||||
assert Exercise.objects.count() == 2
|
|
||||||
assert Set.objects.count() == 9
|
|
||||||
|
|
||||||
def test_import_with_other_data(self):
|
|
||||||
Exercise.objects.create(names=['incline bench press'])
|
|
||||||
e = Exercise.objects.create(names=['flat barbell bench press'])
|
|
||||||
Set.objects.create(exercise=e, weight_kg=100, reps=10, date='2014-01-01', user=self.user)
|
|
||||||
import_fitnotes_db('lifting/testdata/example.fitnotes', self.user)
|
|
||||||
assert Exercise.objects.count() == 3
|
|
||||||
assert Set.objects.count() == 10
|
|
||||||
|
|
||||||
def test_bad_import(self):
|
|
||||||
# good db then bad db, should fail without screwing up existing data
|
|
||||||
import_fitnotes_db('lifting/testdata/example.fitnotes', self.user)
|
|
||||||
with self.assertRaises(Exception):
|
|
||||||
# baddata.fitnotes has all exercise ids set to 9999
|
|
||||||
import_fitnotes_db('lifting/testdata/baddata.fitnotes', self.user)
|
|
||||||
assert Exercise.objects.count() == 2
|
|
||||||
assert Set.objects.count() == 9
|
|
||||||
|
@ -10,5 +10,5 @@ urlpatterns = [
|
|||||||
url(r'^lifts/$', views.lift_list, name='lift-list'),
|
url(r'^lifts/$', views.lift_list, name='lift-list'),
|
||||||
url(r'^lifts/(?P<lift_id>\d+)/$', views.by_lift, name='lift-detail'),
|
url(r'^lifts/(?P<lift_id>\d+)/$', views.by_lift, name='lift-detail'),
|
||||||
|
|
||||||
url(r'^fitnotes/$', views.fitnotes_upload),
|
url(r'^edit/$', views.edit_profile, name='edit-profile'),
|
||||||
]
|
]
|
||||||
|
@ -11,7 +11,8 @@ from django.views.generic import dates
|
|||||||
from django.db.models import Count, Max
|
from django.db.models import Count, Max
|
||||||
|
|
||||||
from . import importers
|
from . import importers
|
||||||
from .models import Set, Exercise
|
from .models import Set
|
||||||
|
from inventory.models import Lift
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ -20,7 +21,7 @@ def month_lifts(request, year, month):
|
|||||||
|
|
||||||
sets_by_day = defaultdict(set)
|
sets_by_day = defaultdict(set)
|
||||||
for workset in Set.objects.filter(user=request.user, date__year=year, date__month=month):
|
for workset in Set.objects.filter(user=request.user, date__year=year, date__month=month):
|
||||||
sets_by_day[workset.date.day].add(workset.exercise)
|
sets_by_day[workset.date.day].add(workset.lift)
|
||||||
date = datetime.date(year, month, 1)
|
date = datetime.date(year, month, 1)
|
||||||
|
|
||||||
# build calendar
|
# build calendar
|
||||||
@ -68,7 +69,7 @@ def day_lifts(request, year, month, day):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def lift_list(request):
|
def lift_list(request):
|
||||||
lifts = Exercise.objects.filter(sets__user=request.user).annotate(
|
lifts = Lift.objects.filter(sets__user=request.user).annotate(
|
||||||
total=Count('sets'), max_kg=Max('sets__weight_kg'),
|
total=Count('sets'), max_kg=Max('sets__weight_kg'),
|
||||||
last_date=Max('sets__date'),
|
last_date=Max('sets__date'),
|
||||||
).order_by('-last_date')
|
).order_by('-last_date')
|
||||||
@ -77,8 +78,8 @@ def lift_list(request):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def by_lift(request, lift_id):
|
def by_lift(request, lift_id):
|
||||||
lift = Exercise.objects.get(pk=lift_id)
|
lift = Lift.objects.get(pk=lift_id)
|
||||||
sets = Set.objects.filter(user=request.user, exercise=lift).order_by('-date')
|
sets = Set.objects.filter(user=request.user, lift=lift).order_by('-date')
|
||||||
return render(request, 'lifting/by_lift.html', {'lift': lift, 'sets': sets})
|
return render(request, 'lifting/by_lift.html', {'lift': lift, 'sets': sets})
|
||||||
|
|
||||||
|
|
||||||
@ -87,18 +88,7 @@ class FitnotesUploadForm(forms.Form):
|
|||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def fitnotes_upload(request):
|
def edit_profile(request):
|
||||||
if request.method == 'POST':
|
form = request.user.profile
|
||||||
form = FitnotesUploadForm(request.POST, request.FILES)
|
|
||||||
if form.is_valid():
|
return render(request, 'profiles/edit.html', {'form': form})
|
||||||
_, fname = tempfile.mkstemp()
|
|
||||||
with open(fname, 'wb') as tmp:
|
|
||||||
for chunk in request.FILES['file'].chunks():
|
|
||||||
tmp.write(chunk)
|
|
||||||
try:
|
|
||||||
importers.import_fitnotes_db(fname, request.user)
|
|
||||||
finally:
|
|
||||||
os.remove(fname)
|
|
||||||
else:
|
|
||||||
form = FitnotesUploadForm()
|
|
||||||
return render(request, 'lifting/fitnotes.html', {'form': form})
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
default_app_config = 'profiles.apps.ProfileConfig'
|
|
@ -1,23 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Profile',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)),
|
|
||||||
('lifting_units', models.CharField(max_length=1, choices=[('m', 'Metric (kg)'), ('i', 'Imperial (lbs)')])),
|
|
||||||
('user', models.OneToOneField(related_name='config', to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,41 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
from django.conf import settings
|
|
||||||
import django.contrib.postgres.fields
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
('profiles', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Bar',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, serialize=False, verbose_name='ID', primary_key=True)),
|
|
||||||
('name', models.CharField(max_length=100)),
|
|
||||||
('weight_kg', models.DecimalField(max_digits=7, decimal_places=3)),
|
|
||||||
('user', models.OneToOneField(related_name='bar', to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='profile',
|
|
||||||
name='plate_pairs',
|
|
||||||
field=django.contrib.postgres.fields.ArrayField(default=['45', '45', '25', '10', '5', '5', '2.5', '1.25'], base_field=models.DecimalField(max_digits=7, decimal_places=3), size=None),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='profile',
|
|
||||||
name='lifting_units',
|
|
||||||
field=models.CharField(default='i', choices=[('m', 'Metric (kg)'), ('i', 'Imperial (lb)')], max_length=1),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='profile',
|
|
||||||
name='user',
|
|
||||||
field=models.OneToOneField(related_name='profile', to=settings.AUTH_USER_MODEL),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,31 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.contrib.postgres.fields import ArrayField
|
|
||||||
from common import to_lb
|
|
||||||
|
|
||||||
UNITS = (
|
|
||||||
('m', 'Metric (kg)'),
|
|
||||||
('i', 'Imperial (lb)'),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Profile(models.Model):
|
|
||||||
user = models.OneToOneField(User, related_name='profile')
|
|
||||||
|
|
||||||
lifting_units = models.CharField(max_length=1, choices=UNITS, default='i')
|
|
||||||
plate_pairs = ArrayField(models.DecimalField(max_digits=7, decimal_places=3),
|
|
||||||
default=['45','45','25','10','5','5','2.5','1.25'])
|
|
||||||
|
|
||||||
|
|
||||||
class Bar(models.Model):
|
|
||||||
user = models.OneToOneField(User, related_name='bar')
|
|
||||||
|
|
||||||
name = models.CharField(max_length=100)
|
|
||||||
weight_kg = models.DecimalField(max_digits=7, decimal_places=3)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return '{} ({}lb / {}kg)'.format(self.name, self.weight_kg, self.weight_lb)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def weight_lb(self):
|
|
||||||
return to_lb(self.weight_kg)
|
|
@ -1,12 +0,0 @@
|
|||||||
from django.test import TestCase
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from .models import Profile
|
|
||||||
|
|
||||||
|
|
||||||
class TestProfile(TestCase):
|
|
||||||
|
|
||||||
def test_signal(self):
|
|
||||||
|
|
||||||
u = User.objects.create_user(username='test', email='test@example.com', password='test')
|
|
||||||
|
|
||||||
assert Profile.objects.filter(user=u).count() == 1
|
|
@ -1,10 +0,0 @@
|
|||||||
from django.shortcuts import render
|
|
||||||
from django import forms
|
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def edit_profile(request):
|
|
||||||
form = request.user.profile
|
|
||||||
|
|
||||||
return render(request, 'profiles/edit.html', {'form': form})
|
|
@ -16,7 +16,7 @@
|
|||||||
<table class="table day-workouts">
|
<table class="table day-workouts">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Exercise</th>
|
<th>Lift</th>
|
||||||
<th>Weight</th>
|
<th>Weight</th>
|
||||||
<th>Reps</th>
|
<th>Reps</th>
|
||||||
<tr>
|
<tr>
|
||||||
@ -24,7 +24,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for set in sets %}
|
{% for set in sets %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{% url 'lift-detail' set.exercise.id %}">{{set.exercise}}</a></td>
|
<td><a href="{% url 'lift-detail' set.lift.id %}">{{set.lift}}</a></td>
|
||||||
<td>{% mass_unit set.weight_kg %}</td>
|
<td>{% mass_unit set.weight_kg %}</td>
|
||||||
<td>{{set.reps}}</td>
|
<td>{{set.reps}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<table class="table lifts-table">
|
<table class="table lifts-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Exercise</th>
|
<th>Lift</th>
|
||||||
<th>Total Sets</th>
|
<th>Total Sets</th>
|
||||||
<th>Last Set</th>
|
<th>Last Set</th>
|
||||||
<th>Max Weight</th>
|
<th>Max Weight</th>
|
||||||
|
@ -21,8 +21,9 @@ INSTALLED_APPS = (
|
|||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'django_gravatar',
|
'django_gravatar',
|
||||||
'profiles',
|
'inventory',
|
||||||
'lifting',
|
'lifting',
|
||||||
|
'fitnotes',
|
||||||
)
|
)
|
||||||
|
|
||||||
MIDDLEWARE_CLASSES = (
|
MIDDLEWARE_CLASSES = (
|
||||||
|
Loading…
Reference in New Issue
Block a user