diff --git a/old-shaft-files/lifting/lifting_tags.py b/old-shaft-files/lifting/lifting_tags.py
new file mode 100644
index 0000000..67dd1b6
--- /dev/null
+++ b/old-shaft-files/lifting/lifting_tags.py
@@ -0,0 +1,15 @@
+from django import template
+from django.template.loader import render_to_string
+
+register = template.Library()
+
+
+@register.filter
+def formfield(value, arg):
+ if ',' in arg:
+ size, units = arg.split(',')
+ else:
+ size = arg
+ units = None
+ return render_to_string('lifting/_form-group.html',
+ {'field': value, 'size': size, 'units': units})
diff --git a/old-shaft-files/lifting/models.py b/old-shaft-files/lifting/models.py
new file mode 100644
index 0000000..de49596
--- /dev/null
+++ b/old-shaft-files/lifting/models.py
@@ -0,0 +1,82 @@
+
+
+class Plan(models.Model):
+ name = models.CharField(max_length=100)
+ tags = models.ManyToManyField(PlanTag, related_name='plans')
+ public = models.BooleanField(default=False)
+ owner = models.ForeignKey(User, related_name='plans', null=True)
+ cloned_from = models.ForeignKey('self', null=True)
+
+ objects = ByNameManager()
+
+ def __str__(self):
+ return '{0}'.format(self.name)
+
+ def get_absolute_url(self):
+ return reverse('lifting.views.plan', args=[str(self.id)])
+
+ def natural_key(self):
+ return (self.name,)
+
+ def get_days(self, user):
+ exercise_rules = {}
+ days = []
+ for day in self.days.all():
+ day_sets = []
+ for exercise in day.exercises.all():
+ for reps in exercise.get_sets():
+ rules = exercise_rules.setdefault(exercise.exercise,
+ exercise.exercise.rules.get(user=user))
+ day_sets.append(
+ Set(exercise=exercise.exercise,
+ weight=round_to(rules.work_weight*exercise.percent, rules.increment),
+ reps=reps,
+ type='planned')
+ )
+ if exercise.raise_weight:
+ rules.work_weight += rules.increment
+ days.append(day_sets)
+ return days
+
+
+class PlanDay(models.Model):
+ plan = models.ForeignKey(Plan, related_name='days')
+ name = models.CharField(max_length=100)
+ order = models.PositiveIntegerField()
+
+ def __str__(self):
+ return '{0}: {1}'.format(self.plan, self.name)
+
+ class Meta:
+ ordering = ['order']
+
+
+class PlanExercise(models.Model):
+ plan_day = models.ForeignKey(PlanDay, related_name='exercises')
+ exercise = models.ForeignKey(Exercise)
+ order = models.PositiveIntegerField()
+ sets = models.CharField(max_length=100)
+ percent = models.DecimalField(max_digits=4, decimal_places=3, default=1)
+ raise_weight = models.BooleanField(default=False)
+
+ def get_sets(self):
+ return [int(s) for s in self.sets.split(',')]
+
+ def get_set_display(self):
+ sets = []
+ last_reps = None
+ last_reps_num = 0
+ for s in self.get_sets():
+ if last_reps and s != last_reps:
+ sets.append('{0}x{1}'.format(last_reps_num, last_reps))
+ last_reps_num = 0
+ last_reps_num += 1
+ last_reps = s
+ sets.append('{0}x{1}'.format(last_reps_num, last_reps))
+ return ', '.join(sets)
+
+ def get_percent_display(self):
+ return '{0:%}'.format(self.percent)
+
+ class Meta:
+ ordering = ['order']
diff --git a/old-shaft-files/lifting/plans.py b/old-shaft-files/lifting/plans.py
new file mode 100644
index 0000000..0365ddd
--- /dev/null
+++ b/old-shaft-files/lifting/plans.py
@@ -0,0 +1,31 @@
+from decimal import Decimal as D
+from .models import ExerciseRules, Set
+from .utils import round_to
+
+
+def warmup_ss(exercise, weight, user):
+ """
+ Starting Strength Warmup
+ ========================
+ (start)x5 x2
+ (40%)x5
+ (60%)x3
+ (80%)x2
+ """
+
+ # get relevant rules
+ rules, _ = ExerciseRules.objects.get_or_create(user=user, exercise=exercise)
+ start = rules.start_weight
+
+ # empty bar sets
+ sets = [Set(exercise=exercise, weight=start, reps=5, type='warmup')]*2
+
+ if D('0.8')*weight < start:
+ return []
+
+ for reps, pct in ((5, D('0.4')), (3, D('0.6')), (2, D('0.8'))):
+ ww = round_to(weight * pct, D('5'))
+ if ww > start:
+ sets.append(Set(exercise=exercise, weight=ww, reps=reps, type='warmup'))
+
+ return sets
diff --git a/old-shaft-files/lifting/tests.py b/old-shaft-files/lifting/tests.py
new file mode 100644
index 0000000..f695f67
--- /dev/null
+++ b/old-shaft-files/lifting/tests.py
@@ -0,0 +1,147 @@
+from decimal import Decimal as D
+from django.test import TestCase
+from django.contrib.auth.models import User
+from .models import Exercise, UserSettings, ExerciseRules, Plan
+from .plans import warmup_ss
+from .utils import round_to
+
+
+def _seteq(s1, exercise, weight, reps):
+ try:
+ assert s1.exercise == exercise
+ assert s1.weight == weight
+ assert s1.reps == reps
+ except AssertionError:
+ raise AssertionError('{0} != {1}x{2}'.format(s1, weight, reps))
+
+
+
+class PlanTests(TestCase):
+
+ def setUp(self):
+ self.user = User.objects.create_user(username='user')
+ self.squat = Exercise.objects.create(name='squat')
+ self.bench = Exercise.objects.create(name='bench press')
+ ExerciseRules.objects.create(user=self.user, exercise=self.squat, work_weight=200)
+ ExerciseRules.objects.create(user=self.user, exercise=self.bench, work_weight=100)
+ self.tm = Plan.objects.create(name='Texas Method')
+
+ a1 = self.tm.days.create(name='A week - Volume day', order=1)
+ a1.exercises.create(exercise=self.squat, order=1, sets='5,5,5,5,5', percent=D('0.85'))
+ a1.exercises.create(exercise=self.bench, order=2, sets='5,5,5,5,5', percent=D('0.85'))
+
+ a2 = self.tm.days.create(name='A week - Recovery day', order=2)
+ a2.exercises.create(exercise=self.squat, order=1, sets='5,5', percent=D('0.8'))
+
+ # exaggerated raise_weight for tests
+ a3 = self.tm.days.create(name='A week - Record day', order=3)
+ a3.exercises.create(exercise=self.squat, order=1, sets='5', percent=D(1),
+ raise_weight=True)
+ a3.exercises.create(exercise=self.bench, order=2, sets='5', percent=D(1),
+ raise_weight=True)
+
+ b1 = self.tm.days.create(name='B week - Volume day', order=4)
+ b1.exercises.create(exercise=self.squat, order=1, sets='5,5,5,5,5', percent=D('0.85'))
+
+ b2 = self.tm.days.create(name='B week - Recovery day', order=5)
+ b2.exercises.create(exercise=self.squat, order=1, sets='5,5', percent=D('0.8'))
+ b2.exercises.create(exercise=self.bench, order=1, sets='5,5', percent=D('0.8'))
+
+ b3 = self.tm.days.create(name='B week - Record day', order=6)
+ b3.exercises.create(exercise=self.squat, order=2, sets='5', percent=D(1))
+
+ def test_get_days(self):
+ a1, a2, a3, b1, b2, b3 = self.tm.get_days(self.user)
+
+ _seteq(a1[0], self.squat, 170, 5)
+ _seteq(a1[1], self.squat, 170, 5)
+ _seteq(a1[2], self.squat, 170, 5)
+ _seteq(a1[3], self.squat, 170, 5)
+ _seteq(a1[4], self.squat, 170, 5)
+ _seteq(a1[5], self.bench, 85, 5)
+ _seteq(a1[6], self.bench, 85, 5)
+ _seteq(a1[7], self.bench, 85, 5)
+ _seteq(a1[8], self.bench, 85, 5)
+ _seteq(a1[9], self.bench, 85, 5)
+
+ _seteq(a2[0], self.squat, 160, 5)
+ _seteq(a2[1], self.squat, 160, 5)
+
+ _seteq(a3[0], self.squat, 200, 5)
+ _seteq(a3[1], self.bench, 100, 5)
+
+ # b record day raised by 5
+ _seteq(b3[0], self.squat, 205, 5)
+
+
+class WarmupTests(TestCase):
+
+ def setUp(self):
+ self.exercise = Exercise.objects.create(name='bench press')
+ self.user = User.objects.create_user(username='user')
+
+ def test_round_to(self):
+ assert round_to(D('21'), D('2.5')) == D('20')
+ assert round_to(D('23'), D('2.5')) == D('22.5')
+ assert round_to(D('23'), D('5')) == D('20')
+ assert round_to(D('24.5'), D('2.5')) == D('22.5')
+ assert round_to(D('24.9'), D('5')) == D('25')
+
+ def test_warmup_ss(self):
+ # <=55: no warmup
+ assert warmup_ss(self.exercise, D('10'), self.user) == []
+ assert warmup_ss(self.exercise, D('55'), self.user) == []
+
+ # 60: just the bar
+ sets = warmup_ss(self.exercise, D('60'), self.user)
+ assert len(sets) == 2, sets
+ _seteq(sets[0], self.exercise, 45, 5)
+ _seteq(sets[1], self.exercise, 45, 5)
+
+ sets = warmup_ss(self.exercise, D('70'), self.user)
+ _seteq(sets[0], self.exercise, 45, 5)
+ _seteq(sets[1], self.exercise, 45, 5)
+ _seteq(sets[2], self.exercise, 55, 2)
+
+ sets = warmup_ss(self.exercise, D('95'), self.user)
+ _seteq(sets[0], self.exercise, 45, 5)
+ _seteq(sets[1], self.exercise, 45, 5)
+ _seteq(sets[2], self.exercise, 55, 3)
+ _seteq(sets[3], self.exercise, 75, 2)
+
+ sets = warmup_ss(self.exercise, D('135'), self.user)
+ _seteq(sets[0], self.exercise, 45, 5)
+ _seteq(sets[1], self.exercise, 45, 5)
+ _seteq(sets[2], self.exercise, 50, 5)
+ _seteq(sets[3], self.exercise, 80, 3)
+ _seteq(sets[4], self.exercise, 105, 2)
+
+ sets = warmup_ss(self.exercise, D('170'), self.user)
+ _seteq(sets[0], self.exercise, 45, 5)
+ _seteq(sets[1], self.exercise, 45, 5)
+ _seteq(sets[2], self.exercise, 65, 5)
+ _seteq(sets[3], self.exercise, 100, 3)
+ _seteq(sets[4], self.exercise, 135, 2)
+
+ sets = warmup_ss(self.exercise, D('250'), self.user)
+ _seteq(sets[0], self.exercise, 45, 5)
+ _seteq(sets[1], self.exercise, 45, 5)
+ _seteq(sets[2], self.exercise, 100, 5)
+ _seteq(sets[3], self.exercise, 150, 3)
+ _seteq(sets[4], self.exercise, 200, 2)
+
+ # slightly crazy jump
+ sets = warmup_ss(self.exercise, D('315'), self.user)
+ _seteq(sets[0], self.exercise, 45, 5)
+ _seteq(sets[1], self.exercise, 45, 5)
+ _seteq(sets[2], self.exercise, 125, 5)
+ _seteq(sets[3], self.exercise, 185, 3)
+ _seteq(sets[4], self.exercise, 250, 2)
+
+ # this is probably crazy & should be fixed
+ sets = warmup_ss(self.exercise, D('500'), self.user)
+ _seteq(sets[0], self.exercise, 45, 5)
+ _seteq(sets[1], self.exercise, 45, 5)
+ _seteq(sets[2], self.exercise, 200, 5)
+ _seteq(sets[3], self.exercise, 300, 3)
+ _seteq(sets[4], self.exercise, 400, 2)
diff --git a/old-shaft-files/lifting/views.py b/old-shaft-files/lifting/views.py
new file mode 100644
index 0000000..e0a3ab9
--- /dev/null
+++ b/old-shaft-files/lifting/views.py
@@ -0,0 +1,56 @@
+from decimal import Decimal as D
+from django.shortcuts import render, get_object_or_404
+from django.views.decorators.http import require_http_methods, require_GET
+from django.contrib.auth.decorators import login_required
+from .models import UserSettings, ExerciseRules, Exercise, Plan
+from .forms import UserSettingsForm, PlanForm
+
+
+
+@require_http_methods(["GET", "POST"])
+@login_required
+def plan_edit(request, id):
+ """ edit details of a plan """
+ plan = get_object_or_404(Plan, pk=id)
+ exercises = Exercise.objects.all()
+ if request.method == 'GET':
+ pform = PlanForm(instance=plan)
+ return render(request, 'lifting/plan_edit.html',
+ {'pform': pform, 'plan': plan, 'exercises': exercises})
+
+
+@require_http_methods(["GET", "POST"])
+@login_required
+def rules(request):
+ """ a view that allows a user to view/edit their list of rules """
+ gr, _ = UserSettings.objects.get_or_create(user=request.user)
+ ers = list(ExerciseRules.objects.filter(user=request.user))
+ exercises = Exercise.objects.exclude(id__in=[er.exercise_id for er in ers])
+ if request.method == 'GET':
+ grform = UserSettingsForm(instance=gr)
+ elif request.method == 'POST':
+ grform = UserSettingsForm(request.POST, instance=gr)
+ if grform.is_valid():
+ grform.save()
+ for exercise, start, inc, work in zip(request.POST.getlist('er.exercise'),
+ request.POST.getlist('er.start_weight'),
+ request.POST.getlist('er.increment'),
+ request.POST.getlist('er.work_weight')):
+ exercise = int(exercise)
+ start = D(start)
+ inc = D(inc)
+ work = D(work)
+
+ er, _ = ExerciseRules.objects.get_or_create(user=request.user,
+ exercise_id=int(exercise))
+ er.start_weight = start
+ er.increment = inc
+ er.work_weight = work
+ er.save()
+ return render(request, 'lifting/rules.html',
+ {'grform': grform, 'exercise_rules': ers, 'exercises': exercises, })
+
+
+def er_row(request, eid):
+ return render(request, 'lifting/_er-row.html',
+ {'er': ExerciseRules(user=request.user, exercise=Exercise.objects.get(pk=eid))})
diff --git a/old-shaft-files/templates/lifting/_er-row.html b/old-shaft-files/templates/lifting/_er-row.html
new file mode 100644
index 0000000..cdc2b5a
--- /dev/null
+++ b/old-shaft-files/templates/lifting/_er-row.html
@@ -0,0 +1,24 @@
+
+
+
+ {{er.exercise}}
+ |
+
+
+
+ lbs
+
+ |
+
+
+
+ lbs
+
+ |
+
+
+
+ lbs
+
+ |
+
diff --git a/old-shaft-files/templates/lifting/_form-group.html b/old-shaft-files/templates/lifting/_form-group.html
new file mode 100644
index 0000000..920ab89
--- /dev/null
+++ b/old-shaft-files/templates/lifting/_form-group.html
@@ -0,0 +1,7 @@
+
diff --git a/old-shaft-files/templates/lifting/edit_plan.html b/old-shaft-files/templates/lifting/edit_plan.html
new file mode 100644
index 0000000..4409486
--- /dev/null
+++ b/old-shaft-files/templates/lifting/edit_plan.html
@@ -0,0 +1,34 @@
+{% extends "lifting/base.html" %}
+{% load lifting %}
+
+{% block content %}
+
+
+
+ {% for day in plan.days.all %}
+
+
+ {{day.name}}
+
+
+ Exercise | Sets | Percent |
+
+
+ {% for e in day.exercises.all %}
+
+ {{e.exercise}} |
+ {{e.get_set_display}} |
+ {{e.get_percent_display}} |
+
+ {% endfor %}
+
+
+
+ {% endfor %}
+
+{% endblock %}
+
+{% block script %}
+
+{% endblock %}
diff --git a/old-shaft-files/templates/lifting/plan.html b/old-shaft-files/templates/lifting/plan.html
new file mode 100644
index 0000000..98ee4b0
--- /dev/null
+++ b/old-shaft-files/templates/lifting/plan.html
@@ -0,0 +1,33 @@
+{% extends "lifting/base.html" %}
+{% load lifting %}
+
+{% block content %}
+{{plan}}
+
+
+ {% for day in plan.days.all %}
+
+
{{day.name}}
+
+
+ Exercise | Sets | Percent |
+
+
+ {% for e in day.exercises.all %}
+
+ {{e.exercise}} |
+ {{e.get_set_display}} |
+ {{e.get_percent_display}} |
+
+ {% endfor %}
+
+
+
+ {% endfor %}
+
+{% endblock %}
+
+{% block script %}
+
+{% endblock %}
diff --git a/old-shaft-files/templates/lifting/plan_edit.html b/old-shaft-files/templates/lifting/plan_edit.html
new file mode 100644
index 0000000..61b77e8
--- /dev/null
+++ b/old-shaft-files/templates/lifting/plan_edit.html
@@ -0,0 +1,54 @@
+{% extends "lifting/base.html" %}
+{% load lifting %}
+
+{% block content %}
+
+
+{% endblock %}
+
+{% block script %}
+
+{% endblock %}
diff --git a/old-shaft-files/templates/lifting/plans.html b/old-shaft-files/templates/lifting/plans.html
new file mode 100644
index 0000000..8d8a525
--- /dev/null
+++ b/old-shaft-files/templates/lifting/plans.html
@@ -0,0 +1,36 @@
+{% extends "lifting/base.html" %}
+{% load lifting %}
+
+{% block content %}
+
+
+
+ Plan |
+ Tags |
+ Popularity |
+ |
+ |
+
+
+
+ {% for plan in plans %}
+
+ {{plan}} |
+
+ {% for tag in plan.tags.all %}
+ {{tag.name}}
+ {% endfor %}
+ |
+ {{plan.popularity}} |
+ clone |
+ use plan |
+
+ {% endfor %}
+
+
+{% endblock content %}
+
+{% block script %}
+
+{% endblock %}
diff --git a/old-shaft-files/templates/lifting/rules.html b/old-shaft-files/templates/lifting/rules.html
new file mode 100644
index 0000000..3dbbe57
--- /dev/null
+++ b/old-shaft-files/templates/lifting/rules.html
@@ -0,0 +1,56 @@
+{% extends "lifting/base.html" %}
+{% load lifting %}
+
+{% block content %}
+
+{% endblock content %}
+
+{% block script %}
+
+{% endblock %}