diff --git a/FitnessTracker/FitnessTracker/__init__.py b/FitnessTracker/FitnessTracker/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/FitnessTracker/FitnessTracker/asgi.py b/FitnessTracker/FitnessTracker/asgi.py
new file mode 100644
index 0000000..8d9d58c
--- /dev/null
+++ b/FitnessTracker/FitnessTracker/asgi.py
@@ -0,0 +1,16 @@
+"""
+ASGI config for FitnessTracker project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'FitnessTracker.settings')
+
+application = get_asgi_application()
diff --git a/FitnessTracker/FitnessTracker/settings.py b/FitnessTracker/FitnessTracker/settings.py
new file mode 100644
index 0000000..bce16bc
--- /dev/null
+++ b/FitnessTracker/FitnessTracker/settings.py
@@ -0,0 +1,140 @@
+"""
+Django settings for FitnessTracker project.
+
+Generated by 'django-admin startproject' using Django 5.1.2.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.1/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/5.1/ref/settings/
+"""
+
+from pathlib import Path
+import os
+from dotenv import load_dotenv
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'django-insecure-!h!g7+aig%0y_v6j)pu#i-i+o)mwg95_z)2b46_9uq=p&*5=ex'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+
+# Application definition
+
+INSTALLED_APPS = [
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'main',
+ 'accounts',
+ 'workouts',
+ 'exercises',
+ 'routines',
+]
+
+MIDDLEWARE = [
+ 'django.middleware.security.SecurityMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ROOT_URLCONF = 'FitnessTracker.urls'
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
+WSGI_APPLICATION = 'FitnessTracker.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': BASE_DIR / 'db.sqlite3',
+ }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/5.1/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'Asia/Riyadh'
+
+USE_I18N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/5.1/howto/static-files/
+
+STATIC_URL = 'static/'
+
+# Default primary key field type
+# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
+
+DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+
+MEDIA_URL = '/media/'
+MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
+
+EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
+EMAIL_HOST = 'smtp.gmail.com'
+EMAIL_PORT = 587
+EMAIL_USE_TLS = True
+EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER")
+EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD")
\ No newline at end of file
diff --git a/FitnessTracker/FitnessTracker/urls.py b/FitnessTracker/FitnessTracker/urls.py
new file mode 100644
index 0000000..06eb050
--- /dev/null
+++ b/FitnessTracker/FitnessTracker/urls.py
@@ -0,0 +1,29 @@
+"""
+URL configuration for FitnessTracker project.
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/5.1/topics/http/urls/
+Examples:
+Function views
+ 1. Add an import: from my_app import views
+ 2. Add a URL to urlpatterns: path('', views.home, name='home')
+Class-based views
+ 1. Add an import: from other_app.views import Home
+ 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
+Including another URLconf
+ 1. Import the include() function: from django.urls import include, path
+ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
+"""
+from django.contrib import admin
+from django.urls import path, include
+from django.conf.urls.static import static
+from . import settings
+
+urlpatterns = [
+ path('admin/', admin.site.urls),
+ path("", include("main.urls")),
+ path("accounts/", include("accounts.urls")),
+ path("exercises/", include("exercises.urls")),
+ path("workouts/", include("workouts.urls")),
+ path("routines/", include("routines.urls")),
+] + static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
diff --git a/FitnessTracker/FitnessTracker/wsgi.py b/FitnessTracker/FitnessTracker/wsgi.py
new file mode 100644
index 0000000..e46d4f6
--- /dev/null
+++ b/FitnessTracker/FitnessTracker/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for FitnessTracker project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'FitnessTracker.settings')
+
+application = get_wsgi_application()
diff --git a/FitnessTracker/accounts/__init__.py b/FitnessTracker/accounts/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/FitnessTracker/accounts/admin.py b/FitnessTracker/accounts/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/FitnessTracker/accounts/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/FitnessTracker/accounts/apps.py b/FitnessTracker/accounts/apps.py
new file mode 100644
index 0000000..3e3c765
--- /dev/null
+++ b/FitnessTracker/accounts/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class AccountsConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'accounts'
diff --git a/FitnessTracker/accounts/migrations/__init__.py b/FitnessTracker/accounts/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/FitnessTracker/accounts/models.py b/FitnessTracker/accounts/models.py
new file mode 100644
index 0000000..71a8362
--- /dev/null
+++ b/FitnessTracker/accounts/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/FitnessTracker/accounts/templates/accounts/signin.html b/FitnessTracker/accounts/templates/accounts/signin.html
new file mode 100644
index 0000000..2d1c1a9
--- /dev/null
+++ b/FitnessTracker/accounts/templates/accounts/signin.html
@@ -0,0 +1,37 @@
+{% extends 'main/base.html' %}
+
+{% block title %} Sign In {% endblock %}
+
+{% block content %}
+
+{% load static %}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/FitnessTracker/accounts/templates/accounts/signup.html b/FitnessTracker/accounts/templates/accounts/signup.html
new file mode 100644
index 0000000..5f195ad
--- /dev/null
+++ b/FitnessTracker/accounts/templates/accounts/signup.html
@@ -0,0 +1,53 @@
+{% extends 'main/base.html' %}
+
+{% block title %} Sign Up {% endblock %}
+
+{% block content %}
+
+{% load static %}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/FitnessTracker/accounts/tests.py b/FitnessTracker/accounts/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/FitnessTracker/accounts/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/FitnessTracker/accounts/urls.py b/FitnessTracker/accounts/urls.py
new file mode 100644
index 0000000..7134799
--- /dev/null
+++ b/FitnessTracker/accounts/urls.py
@@ -0,0 +1,10 @@
+from django.urls import path
+from . import views
+
+app_name = "accounts"
+
+urlpatterns = [
+ path('signup/', views.sign_up, name="sign_up"),
+ path('signin/', views.sign_in, name="sign_in"),
+ path('logout/', views.log_out, name="log_out"),
+]
\ No newline at end of file
diff --git a/FitnessTracker/accounts/views.py b/FitnessTracker/accounts/views.py
new file mode 100644
index 0000000..0666442
--- /dev/null
+++ b/FitnessTracker/accounts/views.py
@@ -0,0 +1,45 @@
+from django.shortcuts import render, redirect
+from django.http import HttpRequest, HttpResponse
+
+from django.contrib.auth.models import User
+
+from django.contrib.auth import authenticate, login, logout
+from django.contrib import messages
+
+
+def sign_up(request: HttpRequest):
+ if request.method == "POST":
+
+ try:
+ new_user = User.objects.create_user(username=request.POST["username"],password=request.POST["password"],email=request.POST["email"], first_name=request.POST["first_name"], last_name=request.POST["last_name"])
+ new_user.save()
+ messages.success(request, "You'r Registered Successfuly!", "alert-success")
+ return redirect("accounts:sign_in")
+ except Exception as e:
+ print(e)
+
+ return render(request, "accounts/signup.html")
+
+
+
+def sign_in(request:HttpRequest):
+ if request.method == "POST":
+ user = authenticate(request, username=request.POST["username"], password=request.POST["password"])
+
+ if user:
+ login(request, user)
+ messages.success(request, "You'r Logged In Successfully!", "alert-success")
+ return redirect(request.GET.get("next", "/"))
+ else:
+ messages.error(request, "Please try again", "alert-danger")
+
+ return render(request, "accounts/signin.html")
+
+
+
+def log_out(request: HttpRequest):
+
+ logout(request)
+ messages.success(request, "You'r Logged Out Successfully", "alert-warning")
+
+ return redirect(request.GET.get("next", "/"))
diff --git a/FitnessTracker/coaches/__init__.py b/FitnessTracker/coaches/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/FitnessTracker/coaches/admin.py b/FitnessTracker/coaches/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/FitnessTracker/coaches/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/FitnessTracker/coaches/apps.py b/FitnessTracker/coaches/apps.py
new file mode 100644
index 0000000..52a222b
--- /dev/null
+++ b/FitnessTracker/coaches/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class CoachesConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'coaches'
diff --git a/FitnessTracker/coaches/forms.py b/FitnessTracker/coaches/forms.py
new file mode 100644
index 0000000..e69de29
diff --git a/FitnessTracker/coaches/migrations/__init__.py b/FitnessTracker/coaches/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/FitnessTracker/coaches/models.py b/FitnessTracker/coaches/models.py
new file mode 100644
index 0000000..71a8362
--- /dev/null
+++ b/FitnessTracker/coaches/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/FitnessTracker/coaches/tests.py b/FitnessTracker/coaches/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/FitnessTracker/coaches/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/FitnessTracker/coaches/urls.py b/FitnessTracker/coaches/urls.py
new file mode 100644
index 0000000..6c50bc2
--- /dev/null
+++ b/FitnessTracker/coaches/urls.py
@@ -0,0 +1,10 @@
+from django.urls import path
+from . import views
+
+
+app_name = "coaches"
+
+urlpatterns = [
+ # path('', views.home_view, name="home_view"),
+ # path('contact/', views.contact_view, name="contact_view"),
+]
\ No newline at end of file
diff --git a/FitnessTracker/coaches/views.py b/FitnessTracker/coaches/views.py
new file mode 100644
index 0000000..91ea44a
--- /dev/null
+++ b/FitnessTracker/coaches/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/FitnessTracker/db.sqlite3 b/FitnessTracker/db.sqlite3
new file mode 100644
index 0000000..0e2bb88
Binary files /dev/null and b/FitnessTracker/db.sqlite3 differ
diff --git a/FitnessTracker/exercises/__init__.py b/FitnessTracker/exercises/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/FitnessTracker/exercises/admin.py b/FitnessTracker/exercises/admin.py
new file mode 100644
index 0000000..dfe269b
--- /dev/null
+++ b/FitnessTracker/exercises/admin.py
@@ -0,0 +1,6 @@
+from django.contrib import admin
+from .models import Exercise, Step
+
+admin.site.register(Exercise)
+
+admin.site.register(Step)
diff --git a/FitnessTracker/exercises/apps.py b/FitnessTracker/exercises/apps.py
new file mode 100644
index 0000000..1036d78
--- /dev/null
+++ b/FitnessTracker/exercises/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class ExercisesConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'exercises'
diff --git a/FitnessTracker/exercises/forms.py b/FitnessTracker/exercises/forms.py
new file mode 100644
index 0000000..d87d530
--- /dev/null
+++ b/FitnessTracker/exercises/forms.py
@@ -0,0 +1,8 @@
+from django import forms
+from .models import Exercise
+
+
+class ExerciseForm(forms.ModelForm):
+ class Meta:
+ model = Exercise
+ fields = "__all__"
\ No newline at end of file
diff --git a/FitnessTracker/exercises/migrations/0001_initial.py b/FitnessTracker/exercises/migrations/0001_initial.py
new file mode 100644
index 0000000..cf49a4f
--- /dev/null
+++ b/FitnessTracker/exercises/migrations/0001_initial.py
@@ -0,0 +1,37 @@
+# Generated by Django 5.1.2 on 2024-11-28 10:27
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Exercise',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=256)),
+ ('description', models.TextField(blank=True)),
+ ('image', models.ImageField(upload_to='media/images/exercises/')),
+ ('video_link', models.URLField(blank=True)),
+ ('video', models.FileField(blank=True, upload_to='media/videos/')),
+ ('workout_category', models.CharField(choices=[('home', 'Home Workout'), ('club', 'Club Workout'), ('both', 'Home and Club Workouts')], max_length=30)),
+ ('exercise_category', models.CharField(choices=[('strength', 'Strength'), ('cardio', 'Cardio'), ('flexibility', 'Flexibility')], max_length=30)),
+ ('equipment_category', models.CharField(choices=[('none', 'No Equipment'), ('machine', 'Machine'), ('both', 'Both')], max_length=30)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Step',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('instruction', models.TextField()),
+ ('exercise', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='exercises.exercise')),
+ ],
+ ),
+ ]
diff --git a/FitnessTracker/exercises/migrations/0002_alter_exercise_equipment_category.py b/FitnessTracker/exercises/migrations/0002_alter_exercise_equipment_category.py
new file mode 100644
index 0000000..4b31fd5
--- /dev/null
+++ b/FitnessTracker/exercises/migrations/0002_alter_exercise_equipment_category.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.1.2 on 2024-12-02 20:33
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('exercises', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='exercise',
+ name='equipment_category',
+ field=models.CharField(choices=[('none', 'No Machine'), ('machine', 'Machine')], max_length=30),
+ ),
+ ]
diff --git a/FitnessTracker/exercises/migrations/__init__.py b/FitnessTracker/exercises/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/FitnessTracker/exercises/models.py b/FitnessTracker/exercises/models.py
new file mode 100644
index 0000000..40035ec
--- /dev/null
+++ b/FitnessTracker/exercises/models.py
@@ -0,0 +1,46 @@
+from django.db import models
+
+class Exercise(models.Model):
+ class WorkoutCategory(models.TextChoices):
+ HOME_WORKOUT = 'home', 'Home Workout'
+ CLUB_WORKOUT = 'club', 'Club Workout'
+ BOTH_WORKOUTS = 'both', 'Home and Club Workouts'
+
+ class EquipmentCategory(models.TextChoices):
+ NO_EQUIPMENT = 'none', 'No Machine'
+ MACHINE = 'machine', 'Machine'
+
+ class ExerciseCategory(models.TextChoices):
+ STRENGTH = 'strength', 'Strength'
+ CARDIO = 'cardio', 'Cardio'
+ FLEXIBILITY = 'flexibility', 'Flexibility'
+
+ name = models.CharField(max_length=256)
+ description = models.TextField(blank=True)
+ image = models.ImageField(upload_to="media/images/exercises/")
+ video_link = models.URLField(blank=True)
+ video = models.FileField(upload_to="media/videos/", blank=True)
+
+ workout_category = models.CharField(
+ max_length=30,
+ choices=WorkoutCategory.choices
+ )
+ equipment_category = models.CharField(
+ max_length=30,
+ choices=EquipmentCategory.choices
+ )
+ exercise_category = models.CharField(
+ max_length=30,
+ choices=ExerciseCategory.choices
+ )
+
+
+ def __str__(self):
+ return self.name
+
+class Step(models.Model):
+ exercise = models.ForeignKey(Exercise, on_delete=models.CASCADE)
+ instruction = models.TextField()
+
+ def __str__(self) -> str:
+ return f'{self.exercise.name}- step id: {self.id}'
diff --git a/FitnessTracker/exercises/templates/exercises/all_exercises.html b/FitnessTracker/exercises/templates/exercises/all_exercises.html
new file mode 100644
index 0000000..48d8f60
--- /dev/null
+++ b/FitnessTracker/exercises/templates/exercises/all_exercises.html
@@ -0,0 +1,58 @@
+{% extends 'main/base.html' %}
+
+{% block title %} All Exercises {% endblock %}
+
+{% block content %}
+
+{% load static %}
+
+
+
+
+ All Exercises
+
+
+
+
+
+ {% include 'exercises/exercises_list.html' %}
+
+
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/FitnessTracker/exercises/templates/exercises/exercise_detail.html b/FitnessTracker/exercises/templates/exercises/exercise_detail.html
new file mode 100644
index 0000000..69d81ee
--- /dev/null
+++ b/FitnessTracker/exercises/templates/exercises/exercise_detail.html
@@ -0,0 +1,58 @@
+{% extends 'main/base.html' %}
+
+{% block title %} {{exercise.name}} Details {% endblock %}
+
+{% block content %}
+
+{% load static %}
+
+
+
+
+ {{exercise.name}} Exercise
+
+
+
+ {% if request.user.is_authenticated %}
+
+
+
+
+
+
+
How To Do It:
+
+
+
+ {% for step in exercise.step_set.all %}
+
+
+ {{step.instruction}}
+
+
+ {% endfor %}
+
+
+ {% else %}
+
+
+ Sorry only registers users can view exercise details!
+
+
+
+ {% endif %}
+
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/FitnessTracker/exercises/templates/exercises/exercise_search.html b/FitnessTracker/exercises/templates/exercises/exercise_search.html
new file mode 100644
index 0000000..f762359
--- /dev/null
+++ b/FitnessTracker/exercises/templates/exercises/exercise_search.html
@@ -0,0 +1,45 @@
+{% extends 'main/base.html' %}
+
+{% block title %} Exercises {% endblock %}
+
+{% block content %}
+
+{% load static %}
+
+
+
+
+
+ Search Results ({{exercises.count}})
+
+
+ Results for: {{request.GET.search}}
+
+
+
+
+
+
+
+
+ {% include 'exercises/exercises_list.html' %}
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/FitnessTracker/exercises/templates/exercises/exercises_list.html b/FitnessTracker/exercises/templates/exercises/exercises_list.html
new file mode 100644
index 0000000..8bbe206
--- /dev/null
+++ b/FitnessTracker/exercises/templates/exercises/exercises_list.html
@@ -0,0 +1,29 @@
+
+
+
+ {% for exercise in exercises %}
+
+
+
+
+
({{exercise.equipment_category}})
+
+
+
+
+ This Exercise Considered {{exercise.get_workout_category_display }}
+
+
+ It Has {{exercise.step_set.all.count}} Guide Instructions
+
+
+
+
+ {% endfor %}
+
+
\ No newline at end of file
diff --git a/FitnessTracker/exercises/templates/exercises/new_exercise.html b/FitnessTracker/exercises/templates/exercises/new_exercise.html
new file mode 100644
index 0000000..9a91a59
--- /dev/null
+++ b/FitnessTracker/exercises/templates/exercises/new_exercise.html
@@ -0,0 +1,79 @@
+{% extends 'main/base.html' %}
+
+{% block title %} Add/Update Exercise {% endblock %}
+
+{% block content %}
+{% load static %}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/FitnessTracker/exercises/templates/exercises/update_exercise.html b/FitnessTracker/exercises/templates/exercises/update_exercise.html
new file mode 100644
index 0000000..e69de29
diff --git a/FitnessTracker/exercises/tests.py b/FitnessTracker/exercises/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/FitnessTracker/exercises/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/FitnessTracker/exercises/urls.py b/FitnessTracker/exercises/urls.py
new file mode 100644
index 0000000..53a1def
--- /dev/null
+++ b/FitnessTracker/exercises/urls.py
@@ -0,0 +1,14 @@
+from django.urls import path
+from . import views
+
+
+app_name = "exercises"
+
+urlpatterns = [
+ path("all/", views.all_exercises_view, name="all_exercises_view"),
+ path("details//", views.exercise_detail_view, name="exercise_detail_view"),
+ path("new/exercise/", views.new_exercise_view, name="new_exercise_view"),
+ path("update//", views.update_exercise_view, name="update_exercise_view"),
+ path("delete//", views.delete_exercise_view, name="delete_exercise_view"),
+ path("search/exercises/", views.search_exercises_view, name="search_exercises_view"),
+]
diff --git a/FitnessTracker/exercises/views.py b/FitnessTracker/exercises/views.py
new file mode 100644
index 0000000..4a546c5
--- /dev/null
+++ b/FitnessTracker/exercises/views.py
@@ -0,0 +1,125 @@
+from django.shortcuts import render, redirect
+from django.http import HttpRequest, HttpResponse
+
+from .models import Exercise, Step
+from .forms import ExerciseForm
+
+from django.contrib import messages
+from django.core.paginator import Paginator
+from django.contrib.auth.decorators import login_required
+
+from django.db import IntegrityError, transaction
+
+from django.contrib import messages
+
+
+def all_exercises_view(request:HttpRequest):
+ exercises = Exercise.objects.all()
+ workout_categories = Exercise.WorkoutCategory.choices
+
+ page_number = request.GET.get("page", 1)
+ paginator = Paginator(exercises, 4)
+ exercises_page = paginator.get_page(page_number)
+
+ return render(request, "exercises/all_exercises.html", {'exercises': exercises_page, 'workout_categories': workout_categories})
+
+def exercise_detail_view(request:HttpRequest, exercise_id:int):
+ exercise = Exercise.objects.get(pk=exercise_id)
+
+ return render(request, "exercises/exercise_detail.html", {'exercise': exercise})
+
+
+def new_exercise_view(request:HttpRequest):
+
+ if not request.user.is_staff:
+ messages.success(request, "Only staff can add exercises!", "alert-warning")
+ return redirect("main:home_view")
+
+ if request.method == 'POST':
+ exercise = Exercise.objects.create(
+ name=request.POST['name'],
+ description=request.POST.get('description', ''),
+ image=request.FILES.get('image'),
+ video_link=request.POST.get('video_link', ''),
+ video=request.FILES.get('video'),
+ workout_category=request.POST['workout_category'],
+ equipment_category=request.POST['equipment_category'],
+ exercise_category=request.POST['exercise_category'],
+ )
+
+ exercise.save()
+ messages.success(request, "Exercise added!", "alert-warning")
+ return redirect('exercises:all_exercises_view')
+
+ return render(request, 'exercises/new_exercise.html')
+
+
+def new_instruction_view(request:HttpRequest, exercise_id:int):
+
+ if not request.user.is_staff:
+ messages.success(request, "Only staff can add instructions!", "alert-warning")
+ return redirect("main:home_view")
+
+ if request.method == 'POST':
+ exercise = Exercise.objects.get(pk=exercise_id)
+ instruction = Step.objects.create(
+ exercise=exercise,
+ instruction=request.POST['instruction']
+ )
+
+ instruction.save()
+ messages.success(request, "instruction added!", "alert-warning")
+
+ return redirect('exercises:exercise_detail_view',exercise_id=exercise_id)
+
+
+def update_exercise_view(request:HttpRequest, exercise_id:int):
+
+ pass
+
+def delete_exercise_view(request:HttpRequest, exercise_id:int):
+ if not request.user.is_staff:
+ messages.success(request, "Only staff can delete exercises!", "alert-warning")
+ return redirect("main:home_view")
+
+ try:
+ exercise = Exercise.objects.get(pk=exercise_id)
+ exercise.delete()
+ messages.success(request, "Deleted Exercise Successfuly!", "alert-success")
+ except Exception as e:
+ print(e)
+ messages.error(request, "Couldn't Delete Exercise!", "alert-danger")
+
+ return redirect("main:home_view")
+
+
+def search_exercises_view(request:HttpRequest):
+ workout_categories = Exercise.WorkoutCategory.choices
+ exercises = Exercise.objects.all()
+
+ filter_by = None
+
+ if "search" in request.GET and len(request.GET["search"]) >= 3:
+ exercises = Exercise.objects.filter(name__icontains=request.GET["search"])
+
+
+ if "filter_workout_category" in request.GET and request.GET["filter_workout_category"] != '':
+ exercises = exercises.filter(workout_category=request.GET['filter_workout_category'])
+ filter_by = request.GET['filter_workout_category']
+
+ if not exercises.exists():
+ messages.error(request, "Couldn't Find Exercise!", "alert-danger")
+ exercises = []
+
+ return render(request, "exercises/exercise_search.html", {'exercises': exercises,
+ 'workout_categories': workout_categories,
+ 'filter_by':filter_by})
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FitnessTracker/main/__init__.py b/FitnessTracker/main/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/FitnessTracker/main/admin.py b/FitnessTracker/main/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/FitnessTracker/main/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/FitnessTracker/main/apps.py b/FitnessTracker/main/apps.py
new file mode 100644
index 0000000..167f044
--- /dev/null
+++ b/FitnessTracker/main/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class MainConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'main'
diff --git a/FitnessTracker/main/forms.py b/FitnessTracker/main/forms.py
new file mode 100644
index 0000000..c103ca4
--- /dev/null
+++ b/FitnessTracker/main/forms.py
@@ -0,0 +1,7 @@
+from django import forms
+from .models import Contact
+
+class ContactForm(forms.ModelForm):
+ class Meta:
+ model = Contact
+ fields = ['first_name', 'last_name', 'email', 'message']
diff --git a/FitnessTracker/main/migrations/0001_initial.py b/FitnessTracker/main/migrations/0001_initial.py
new file mode 100644
index 0000000..d147e8f
--- /dev/null
+++ b/FitnessTracker/main/migrations/0001_initial.py
@@ -0,0 +1,25 @@
+# Generated by Django 5.1.2 on 2024-11-28 10:27
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Contact',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('first_name', models.CharField(max_length=1024)),
+ ('last_name', models.CharField(max_length=1024)),
+ ('email', models.EmailField(max_length=254)),
+ ('message', models.TextField()),
+ ('created_at', models.DateTimeField(auto_now=True)),
+ ],
+ ),
+ ]
diff --git a/FitnessTracker/main/migrations/__init__.py b/FitnessTracker/main/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/FitnessTracker/main/models.py b/FitnessTracker/main/models.py
new file mode 100644
index 0000000..04a19d5
--- /dev/null
+++ b/FitnessTracker/main/models.py
@@ -0,0 +1,12 @@
+from django.db import models
+
+class Contact(models.Model):
+
+ first_name = models.CharField(max_length=1024)
+ last_name = models.CharField(max_length=1024)
+ email = models.EmailField()
+ message = models.TextField()
+ created_at = models.DateTimeField(auto_now=True)
+
+ def __str__(self) -> str:
+ return self.first_name
\ No newline at end of file
diff --git a/FitnessTracker/main/static/css/style.css b/FitnessTracker/main/static/css/style.css
new file mode 100644
index 0000000..d6cd045
--- /dev/null
+++ b/FitnessTracker/main/static/css/style.css
@@ -0,0 +1,160 @@
+body {
+
+ background-color: #ffce47cb;
+}
+
+a {
+ text-decoration: none;
+}
+
+h1 {
+ font-size: 4.5rem;
+ color: #ffbb00;
+}
+
+h1, h2 {
+ word-spacing: 4px;
+}
+
+main {
+ min-height: 100vh;
+ background-image: url('/static/images/gym-equipment.jpg');
+ background-size: cover;
+ background-position:center;
+ background-repeat: repeat-y;
+}
+
+main {
+ color: #fcd15c;
+}
+
+main a{
+ color: #fcd15c;
+}
+
+footer a{
+ color: black;
+}
+
+
+.transparent-bg {
+ background-color: rgba(83, 83, 83, 0.3);
+ border-radius: 8px;
+ padding: 20px;
+ backdrop-filter: blur(2px);
+}
+
+.table {
+ border-radius: 0.5rem;
+ overflow: hidden;
+}
+
+.label-bg {
+ background-color: #ffce4790;
+ border-radius: 8px;
+ padding: 10px;
+ margin: 10px;
+ backdrop-filter: blur(2px);
+}
+
+.routine_card {
+ background-color: rgba(102, 102, 102, 0.3);
+ backdrop-filter: blur(6px);
+ box-shadow: 0 4px 20px rgba(255, 255, 255, 0.3);
+ transition: transform 0.5s ease-in-out, box-shadow 0.5s ease-in-out, border 0.5s ease-in-out, backdrop-filter 0.5s ease-in-out, background-color 0.5 ease-in-out;
+}
+
+.routine_card:hover {
+ transform: scale(1.03);
+ box-shadow: 9px 9px #ffffffaf;
+ border: 6px solid #ffffff;
+ background-color: rgba(83, 83, 83, 0.143);
+ backdrop-filter: blur(2px);
+}
+
+.line {
+ width: 100%;
+ height: 5px;
+ background-color: #ffffff;
+ margin: 20px 0;
+ margin-bottom: 10px;
+}
+
+.routine_card h2{
+ font-size: 2.5rem;
+ color: #ffbb00;
+ transition: transform 0.3s ease;
+}
+
+.routine_card h2:hover {
+ transform: scale(1.03);
+ color: #ffcf4c;
+}
+
+.routine_card p{
+ font-size: 1.2rem;
+
+}
+
+.routine_card {
+ color: #ffde82;
+}
+
+.exercise_card {
+ background-color: rgba(83, 83, 83, 0.143);
+ backdrop-filter: blur(4px);
+ transition: border 0.3s ease-in-out, backdrop-filter 0.3s ease-in-out, background-color 0.3 ease-in-out;
+}
+
+.exercise_card:hover {
+ background-color: rgba(102, 102, 102, 0.3);
+ backdrop-filter: blur(2px);
+}
+
+.hero {
+ position: relative;
+ background-image: url('/static/images/3d-gym-equipment.jpg');
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+ padding-top: 5rem;
+ padding-bottom: 5rem;
+ padding-left: 2rem;
+ /* border-radius: 1rem; */
+}
+
+.hero::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.466);
+ z-index: 1;
+ /* border-radius: 1rem; */
+}
+
+.right-hero {
+ z-index: 2;
+}
+
+.right-hero h2 {
+ font-size: 3rem;
+}
+
+.left-hero{
+ color: #ffde82;
+ z-index: 2;
+}
+
+@media (max-width: 992px) {
+
+ main {
+ background-size: contain;
+ background-repeat:repeat-y;
+ }
+
+
+}
+
diff --git a/FitnessTracker/main/static/images/3d-gym-equipment.jpg b/FitnessTracker/main/static/images/3d-gym-equipment.jpg
new file mode 100644
index 0000000..170aad8
Binary files /dev/null and b/FitnessTracker/main/static/images/3d-gym-equipment.jpg differ
diff --git a/FitnessTracker/main/static/images/gym-equipment.jpg b/FitnessTracker/main/static/images/gym-equipment.jpg
new file mode 100644
index 0000000..7e8ce00
Binary files /dev/null and b/FitnessTracker/main/static/images/gym-equipment.jpg differ
diff --git a/FitnessTracker/main/templates/main/base.html b/FitnessTracker/main/templates/main/base.html
new file mode 100644
index 0000000..4871400
--- /dev/null
+++ b/FitnessTracker/main/templates/main/base.html
@@ -0,0 +1,148 @@
+{% load static %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% block title %} {% endblock %}
+
+
+
+
+
+
FitnessTracker
+
+
+
+
+ {% url 'main:contact_view' as contact_url %}
+ {% url 'routines:new_routine_view' as new_routine %}
+ {% url 'routines:all_routines_view' as all_routines %}
+ {% url 'exercises:all_exercises_view' as all_exercises %}
+ {% url 'exercises:new_exercise_view' as new_exercise %}
+
+
+
+
+
+
+
+
+
+
+
+ {% if messages %}
+ {% for message in messages %}
+
+ {{message}}
+
+
+ {% endfor %}
+ {% endif %}
+
+ {% block content %}
+
+ {% endblock %}
+
+
+
+
+
\ No newline at end of file
diff --git a/FitnessTracker/main/templates/main/contact.html b/FitnessTracker/main/templates/main/contact.html
new file mode 100644
index 0000000..7369f67
--- /dev/null
+++ b/FitnessTracker/main/templates/main/contact.html
@@ -0,0 +1,34 @@
+{% extends 'main/base.html' %}
+
+{% block title %} Contact Us {% endblock %}
+
+{% block content %}
+
+{% load static %}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/FitnessTracker/main/templates/main/home.html b/FitnessTracker/main/templates/main/home.html
new file mode 100644
index 0000000..1099952
--- /dev/null
+++ b/FitnessTracker/main/templates/main/home.html
@@ -0,0 +1,74 @@
+{% extends 'main/base.html' %}
+
+{% block title %} Fitness Tracker {% endblock %}
+
+{% block content %}
+
+{% load static %}
+
+{% if request.user == user %}
+
+
+
+
+
+ The FitnessTracker is designed to help gym enthusiasts log and manage their fitness routines.
+ This system simplifies workout tracking by offering features for creating reusable workout templates,
+ logging workout sessions, and organizing exercises with detailed instructions
+
+
+
+
+
+ {% if request.user.is_authenticated %}
+
+
+
+
+ Explore Other Routines
+
+
+
+ {% include 'routines/explore_routines.html' %}
+
+ {% endif %}
+
+
+
+
+{% endif %}
+
+
+
+
+ Top Exercises
+
+
+
+ {% include 'exercises/exercises_list.html' %}
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/FitnessTracker/main/tests.py b/FitnessTracker/main/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/FitnessTracker/main/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/FitnessTracker/main/urls.py b/FitnessTracker/main/urls.py
new file mode 100644
index 0000000..36af51f
--- /dev/null
+++ b/FitnessTracker/main/urls.py
@@ -0,0 +1,10 @@
+from django.urls import path
+from . import views
+
+
+app_name = "main"
+
+urlpatterns = [
+ path('', views.home_view, name="home_view"),
+ path('contact/', views.contact_view, name="contact_view"),
+]
\ No newline at end of file
diff --git a/FitnessTracker/main/views.py b/FitnessTracker/main/views.py
new file mode 100644
index 0000000..8bfae4e
--- /dev/null
+++ b/FitnessTracker/main/views.py
@@ -0,0 +1,33 @@
+from django.shortcuts import render, redirect
+from django.http import HttpRequest
+
+from datetime import datetime
+
+from .forms import ContactForm
+
+from routines.models import Routine
+from exercises.models import Exercise, Step
+
+from django.contrib import messages
+
+
+def home_view(request:HttpRequest):
+ routines = Routine.objects.filter(is_public=True)
+ exercises = Exercise.objects.all()[0:4]
+
+ return render(request, 'main/home.html', {'routines': routines, 'exercises': exercises, 'user': request.user})
+
+def contact_view(request:HttpRequest):
+ contact_form = ContactForm()
+
+ if request.method == "POST":
+ contact_form = ContactForm(request.POST)
+ if contact_form.is_valid():
+ contact_form.save()
+ messages.success(request, "Your Message Has Been Sent Successfully!", "alert-success")
+ return redirect('main:home_view')
+ else:
+ messages.error(request, "Couldn't Send Your Message!", "alert-danger")
+ return render(request, "main/contact.html")
+
+ return render(request, 'main/contact.html')
\ No newline at end of file
diff --git a/FitnessTracker/manage.py b/FitnessTracker/manage.py
new file mode 100644
index 0000000..cbbd191
--- /dev/null
+++ b/FitnessTracker/manage.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+ """Run administrative tasks."""
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'FitnessTracker.settings')
+ try:
+ from django.core.management import execute_from_command_line
+ except ImportError as exc:
+ raise ImportError(
+ "Couldn't import Django. Are you sure it's installed and "
+ "available on your PYTHONPATH environment variable? Did you "
+ "forget to activate a virtual environment?"
+ ) from exc
+ execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/FitnessTracker/media/media/images/exercises/Butterfly_Pec_Deck.jpg b/FitnessTracker/media/media/images/exercises/Butterfly_Pec_Deck.jpg
new file mode 100644
index 0000000..a3617ac
Binary files /dev/null and b/FitnessTracker/media/media/images/exercises/Butterfly_Pec_Deck.jpg differ
diff --git a/FitnessTracker/media/media/images/exercises/Cable_Core_Palloff_Press.jpg b/FitnessTracker/media/media/images/exercises/Cable_Core_Palloff_Press.jpg
new file mode 100644
index 0000000..8c33e84
Binary files /dev/null and b/FitnessTracker/media/media/images/exercises/Cable_Core_Palloff_Press.jpg differ
diff --git a/FitnessTracker/media/media/images/exercises/Cable_Crunch.jpg b/FitnessTracker/media/media/images/exercises/Cable_Crunch.jpg
new file mode 100644
index 0000000..09878a9
Binary files /dev/null and b/FitnessTracker/media/media/images/exercises/Cable_Crunch.jpg differ
diff --git a/FitnessTracker/media/media/images/exercises/Calf_Extension.jpg b/FitnessTracker/media/media/images/exercises/Calf_Extension.jpg
new file mode 100644
index 0000000..d356539
Binary files /dev/null and b/FitnessTracker/media/media/images/exercises/Calf_Extension.jpg differ
diff --git a/FitnessTracker/media/media/images/exercises/Crunch_Machine.jpg b/FitnessTracker/media/media/images/exercises/Crunch_Machine.jpg
new file mode 100644
index 0000000..500a258
Binary files /dev/null and b/FitnessTracker/media/media/images/exercises/Crunch_Machine.jpg differ
diff --git a/FitnessTracker/media/media/images/exercises/Elliptical_Trainer.jpg b/FitnessTracker/media/media/images/exercises/Elliptical_Trainer.jpg
new file mode 100644
index 0000000..a1d29bb
Binary files /dev/null and b/FitnessTracker/media/media/images/exercises/Elliptical_Trainer.jpg differ
diff --git a/FitnessTracker/media/media/images/exercises/Hip_Abduction.jpg b/FitnessTracker/media/media/images/exercises/Hip_Abduction.jpg
new file mode 100644
index 0000000..6467bc0
Binary files /dev/null and b/FitnessTracker/media/media/images/exercises/Hip_Abduction.jpg differ
diff --git a/FitnessTracker/media/media/images/exercises/Lat_Pulldown_Cable.jpg b/FitnessTracker/media/media/images/exercises/Lat_Pulldown_Cable.jpg
new file mode 100644
index 0000000..6b43f06
Binary files /dev/null and b/FitnessTracker/media/media/images/exercises/Lat_Pulldown_Cable.jpg differ
diff --git a/FitnessTracker/media/media/images/exercises/Triceps_Pushdown.jpg b/FitnessTracker/media/media/images/exercises/Triceps_Pushdown.jpg
new file mode 100644
index 0000000..7f29dc9
Binary files /dev/null and b/FitnessTracker/media/media/images/exercises/Triceps_Pushdown.jpg differ
diff --git a/FitnessTracker/media/media/images/exercises/back-extension.jpg b/FitnessTracker/media/media/images/exercises/back-extension.jpg
new file mode 100644
index 0000000..d71602a
Binary files /dev/null and b/FitnessTracker/media/media/images/exercises/back-extension.jpg differ
diff --git a/FitnessTracker/media/media/images/exercises/chest-press.jpg b/FitnessTracker/media/media/images/exercises/chest-press.jpg
new file mode 100644
index 0000000..6cc0c0b
Binary files /dev/null and b/FitnessTracker/media/media/images/exercises/chest-press.jpg differ
diff --git a/FitnessTracker/routines/__init__.py b/FitnessTracker/routines/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/FitnessTracker/routines/admin.py b/FitnessTracker/routines/admin.py
new file mode 100644
index 0000000..98073b7
--- /dev/null
+++ b/FitnessTracker/routines/admin.py
@@ -0,0 +1,7 @@
+from django.contrib import admin
+from .models import Routine
+
+
+admin.site.register(Routine)
+
+# Register your models here.
diff --git a/FitnessTracker/routines/apps.py b/FitnessTracker/routines/apps.py
new file mode 100644
index 0000000..f20474e
--- /dev/null
+++ b/FitnessTracker/routines/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class RoutinesConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'routines'
diff --git a/FitnessTracker/routines/migrations/0001_initial.py b/FitnessTracker/routines/migrations/0001_initial.py
new file mode 100644
index 0000000..37e73a5
--- /dev/null
+++ b/FitnessTracker/routines/migrations/0001_initial.py
@@ -0,0 +1,22 @@
+# Generated by Django 5.1.2 on 2024-11-28 10:27
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Routine',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=256)),
+ ('all_weights', models.PositiveBigIntegerField(blank=True)),
+ ],
+ ),
+ ]
diff --git a/FitnessTracker/routines/migrations/0002_alter_routine_all_weights.py b/FitnessTracker/routines/migrations/0002_alter_routine_all_weights.py
new file mode 100644
index 0000000..cf16b94
--- /dev/null
+++ b/FitnessTracker/routines/migrations/0002_alter_routine_all_weights.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.1.2 on 2024-12-01 09:17
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('routines', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='routine',
+ name='all_weights',
+ field=models.PositiveBigIntegerField(blank=True, default=0),
+ ),
+ ]
diff --git a/FitnessTracker/routines/migrations/0003_routine_is_public.py b/FitnessTracker/routines/migrations/0003_routine_is_public.py
new file mode 100644
index 0000000..d980636
--- /dev/null
+++ b/FitnessTracker/routines/migrations/0003_routine_is_public.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.1.2 on 2024-12-02 22:34
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('routines', '0002_alter_routine_all_weights'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='routine',
+ name='is_public',
+ field=models.BooleanField(default=True),
+ ),
+ ]
diff --git a/FitnessTracker/routines/migrations/0004_routine_user.py b/FitnessTracker/routines/migrations/0004_routine_user.py
new file mode 100644
index 0000000..0eaefe9
--- /dev/null
+++ b/FitnessTracker/routines/migrations/0004_routine_user.py
@@ -0,0 +1,22 @@
+# Generated by Django 5.1.2 on 2024-12-02 23:18
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('routines', '0003_routine_is_public'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='routine',
+ name='user',
+ field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
+ preserve_default=False,
+ ),
+ ]
diff --git a/FitnessTracker/routines/migrations/0005_remove_routine_all_weights.py b/FitnessTracker/routines/migrations/0005_remove_routine_all_weights.py
new file mode 100644
index 0000000..dfa2cdd
--- /dev/null
+++ b/FitnessTracker/routines/migrations/0005_remove_routine_all_weights.py
@@ -0,0 +1,17 @@
+# Generated by Django 5.1.2 on 2024-12-04 18:20
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('routines', '0004_routine_user'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='routine',
+ name='all_weights',
+ ),
+ ]
diff --git a/FitnessTracker/routines/migrations/__init__.py b/FitnessTracker/routines/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/FitnessTracker/routines/models.py b/FitnessTracker/routines/models.py
new file mode 100644
index 0000000..516702e
--- /dev/null
+++ b/FitnessTracker/routines/models.py
@@ -0,0 +1,11 @@
+from django.db import models
+from django.contrib.auth.models import User
+
+class Routine(models.Model):
+ name = models.CharField(max_length=256)
+ user = models.ForeignKey(User, on_delete=models.CASCADE)
+
+ is_public = models.BooleanField(default=True)
+
+ def __str__(self) -> str:
+ return self.name
\ No newline at end of file
diff --git a/FitnessTracker/routines/templates/routines/all_routines.html b/FitnessTracker/routines/templates/routines/all_routines.html
new file mode 100644
index 0000000..3916b64
--- /dev/null
+++ b/FitnessTracker/routines/templates/routines/all_routines.html
@@ -0,0 +1,51 @@
+{% extends 'main/base.html' %}
+
+{% block title %} All Routines {% endblock %}
+
+{% block content %}
+
+{% load static %}
+
+
+
+
+ {% include 'routines/routines_list.html' %}
+
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/FitnessTracker/routines/templates/routines/explore_routines.html b/FitnessTracker/routines/templates/routines/explore_routines.html
new file mode 100644
index 0000000..182a6b6
--- /dev/null
+++ b/FitnessTracker/routines/templates/routines/explore_routines.html
@@ -0,0 +1,43 @@
+
+
+
+ {% if request.user.is_authenticated %}
+ {% for routine in routines %}
+
+
+
+
+ {{routine.name}}
+
+
{{routine.workout_set.all.count}} Workouts
+
+
+
Exercises
+
+ {% for workout in routine.workout_set.all %}
+
+ {{workout.exercise.name}}
+
+ {% endfor %}
+
+
+
+
By {{routine.user.first_name}}
+
+
+
+ {% empty %}
+
+ {% endfor %}
+ {% endif %}
+
+
\ No newline at end of file
diff --git a/FitnessTracker/routines/templates/routines/new_routine.html b/FitnessTracker/routines/templates/routines/new_routine.html
new file mode 100644
index 0000000..5ab90db
--- /dev/null
+++ b/FitnessTracker/routines/templates/routines/new_routine.html
@@ -0,0 +1,43 @@
+{% extends 'main/base.html' %}
+
+{% block title %} New Routine {% endblock %}
+
+{% block content %}
+
+{% load static %}
+
+
+
+ Create New Workouts Routine
+
+
+
+
+
+{% endblock %}
diff --git a/FitnessTracker/routines/templates/routines/routine_details.html b/FitnessTracker/routines/templates/routines/routine_details.html
new file mode 100644
index 0000000..5c45ab5
--- /dev/null
+++ b/FitnessTracker/routines/templates/routines/routine_details.html
@@ -0,0 +1,25 @@
+{% extends 'main/base.html' %}
+
+{% block title %} {{routine.name}} Details {% endblock %}
+
+{% block content %}
+
+{% load static %}
+
+
+
+
+
+ {{routine.name}} Workouts
+
+
+
+
+ {% include 'workouts/workouts_list.html' %}
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/FitnessTracker/routines/templates/routines/routines_list.html b/FitnessTracker/routines/templates/routines/routines_list.html
new file mode 100644
index 0000000..c84e07f
--- /dev/null
+++ b/FitnessTracker/routines/templates/routines/routines_list.html
@@ -0,0 +1,41 @@
+
+
+
+ {% if request.user.is_authenticated %}
+ {% if request.user == user %}
+ {% for routine in routines %}
+
+
+
+
+ {{routine.name}}
+
+
{{routine.workout_set.all.count}} Workouts
+
+
+
Exercises
+
+ {% for workout in routine.workout_set.all %}
+
+ {{workout.exercise.name}}
+
+ {% endfor %}
+
+
+
+ {% empty %}
+
+ {% endfor %}
+ {% endif %}
+ {% endif %}
+
+
\ No newline at end of file
diff --git a/FitnessTracker/routines/templates/routines/search_routine.html b/FitnessTracker/routines/templates/routines/search_routine.html
new file mode 100644
index 0000000..258a38f
--- /dev/null
+++ b/FitnessTracker/routines/templates/routines/search_routine.html
@@ -0,0 +1,36 @@
+{% extends 'main/base.html' %}
+
+{% block title %} All Routines {% endblock %}
+
+{% block content %}
+
+{% load static %}
+
+
+
+
+ {% include 'routines/routines_list.html' %}
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/FitnessTracker/routines/templates/routines/update_routine.html b/FitnessTracker/routines/templates/routines/update_routine.html
new file mode 100644
index 0000000..9a4584f
--- /dev/null
+++ b/FitnessTracker/routines/templates/routines/update_routine.html
@@ -0,0 +1,42 @@
+{% extends 'main/base.html' %}
+
+{% block title %} Update Routine {% endblock %}
+
+{% block content %}
+
+{% load static %}
+
+
+
+ Update Your Workout Routine
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/FitnessTracker/routines/tests.py b/FitnessTracker/routines/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/FitnessTracker/routines/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/FitnessTracker/routines/urls.py b/FitnessTracker/routines/urls.py
new file mode 100644
index 0000000..06d5562
--- /dev/null
+++ b/FitnessTracker/routines/urls.py
@@ -0,0 +1,14 @@
+from django.urls import path
+from . import views
+
+
+app_name = "routines"
+
+urlpatterns = [
+ path("all/", views.all_routines_view, name="all_routines_view"),
+ path("new/routine/", views.new_routine_view, name="new_routine_view"),
+ path("update//", views.update_routine_view, name="update_routine_view"),
+ path("delete//", views.delete_routine_view, name="delete_routine_view"),
+ path("detail/", views.routine_detail_view, name="routine_detail_view"),
+ path("search/routines/", views.search_routines_view, name="search_routines_view"),
+]
\ No newline at end of file
diff --git a/FitnessTracker/routines/views.py b/FitnessTracker/routines/views.py
new file mode 100644
index 0000000..602a44b
--- /dev/null
+++ b/FitnessTracker/routines/views.py
@@ -0,0 +1,125 @@
+from django.shortcuts import render, redirect
+from django.http import HttpRequest, HttpResponse
+
+# from .forms import RoutineForm
+from .models import Routine
+
+from django.contrib.auth.models import User
+
+from workouts.models import Workout, Set, Done
+
+from exercises.models import Exercise
+
+from django.contrib import messages
+from django.core.paginator import Paginator
+from django.contrib.auth.decorators import login_required
+
+from django.db import transaction
+from django.utils import timezone
+
+
+def all_routines_view(request:HttpRequest):
+ if not request.user.is_authenticated:
+ messages.warning(request, "Only registered users can see their routines", "alert-warning")
+ return redirect("main:home_view")
+
+
+ routines = Routine.objects.filter(user=request.user)
+
+ page_number = request.GET.get("page", 1)
+ paginator = Paginator(routines, 4)
+ routines_page = paginator.get_page(page_number)
+
+ return render(request, "routines/all_routines.html", {'routines': routines_page})
+
+def profile_view(request:HttpRequest, user_id:int):
+ user = User.objects.get(pk=user_id)
+ routines = Routine.objects.filter(user=user, is_public=True)
+
+ return render(request, "routines/profile.html", {'routines': routines, 'user':user})
+
+
+def routine_detail_view(request:HttpRequest, routine_id:int):
+ routine = Routine.objects.get(pk=routine_id)
+
+ exercises = Exercise.objects.all()
+ checks = Done.objects.all()
+
+ workouts = routine.workout_set.all()
+
+ return render(request, "routines/routine_details.html", {'routine': routine,
+ 'checks': checks,
+ 'exercises':exercises,
+ 'workouts': workouts})
+
+@login_required
+def new_routine_view(request:HttpRequest):
+
+ if request.method == "POST":
+ name = request.POST.get("name", "").strip()
+ is_public = request.POST.get("is_public") == "1"
+
+ if name:
+ with transaction.atomic():
+ routine:Routine = Routine(user=request.user, name=request.POST['name'], is_public=is_public)
+ routine.save()
+ messages.success(request, "Continue To Add Your Workouts!", "alert-success")
+ return redirect("routines:routine_detail_view", routine_id=routine.id)
+ else:
+ print("not valid form", routine.errors)
+ messages.error(request, "Couldn't Able To Add The title!", "alert-danger")
+
+ return render(request, "routines/new_routine.html")
+
+@login_required
+def update_routine_view(request, routine_id):
+ routine = Routine.objects.get(pk=routine_id)
+
+ if request.method == "POST":
+ name = request.POST.get("name", "").strip()
+ is_public = request.POST.get("is_public") == "1"
+
+ if name:
+ with transaction.atomic():
+ routine.name = name
+ routine.is_public = is_public
+ routine.save()
+ messages.success(request, "Routine updated successfully!", "alert-success")
+ return redirect("routines:routine_detail_view", routine_id=routine.id)
+ else:
+ messages.error(request, "Couldn't update the routine title!", "alert-danger")
+
+ return render(request, "routines/update_routine.html", {'routine': routine})
+
+
+@login_required
+def delete_routine_view(request:HttpRequest, routine_id:int):
+ try:
+ routine = Routine.objects.get(pk=routine_id)
+ routine.delete()
+ messages.success(request, "Deleted Routine Successfully!", "alert-success")
+ except Exception as e:
+ print(e)
+ messages.error(request, "Couldn't Delete Routine!", "alert-danger")
+
+ return redirect("routines:all_routines_view")
+
+
+def search_routines_view(request:HttpRequest):
+ if "search" in request.GET and len(request.GET["search"]) >= 3:
+ routines = Routine.objects.filter(name__icontains=request.GET["search"])
+
+ else:
+ messages.error(request, "Couldn't Find Routine!", "alert-danger")
+ routines = []
+
+ return render(request, "routines/search_routine.html", {'routines': routines,})
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FitnessTracker/workouts/__init__.py b/FitnessTracker/workouts/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/FitnessTracker/workouts/admin.py b/FitnessTracker/workouts/admin.py
new file mode 100644
index 0000000..c58e565
--- /dev/null
+++ b/FitnessTracker/workouts/admin.py
@@ -0,0 +1,10 @@
+from django.contrib import admin
+from .models import Workout, Set, Done
+
+
+admin.site.register(Workout)
+admin.site.register(Set)
+admin.site.register(Done)
+
+
+# Register your models here.
diff --git a/FitnessTracker/workouts/apps.py b/FitnessTracker/workouts/apps.py
new file mode 100644
index 0000000..b89e71a
--- /dev/null
+++ b/FitnessTracker/workouts/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class WorkoutsConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'workouts'
diff --git a/FitnessTracker/workouts/forms.py b/FitnessTracker/workouts/forms.py
new file mode 100644
index 0000000..3690677
--- /dev/null
+++ b/FitnessTracker/workouts/forms.py
@@ -0,0 +1,10 @@
+from django import forms
+from .models import Workout
+
+
+class WorkoutForm(forms.ModelForm):
+ class Meta:
+ model = Workout
+ fields = ['exercise', 'note']
+
+
\ No newline at end of file
diff --git a/FitnessTracker/workouts/migrations/0001_initial.py b/FitnessTracker/workouts/migrations/0001_initial.py
new file mode 100644
index 0000000..ffaa7c6
--- /dev/null
+++ b/FitnessTracker/workouts/migrations/0001_initial.py
@@ -0,0 +1,38 @@
+# Generated by Django 5.1.2 on 2024-11-28 10:27
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('exercises', '0001_initial'),
+ ('routines', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Workout',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('note', models.TextField(blank=True)),
+ ('restTime', models.PositiveIntegerField(blank=True)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('exercise', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='exercises.exercise')),
+ ('routine', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='routines.routine')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Set',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('weight', models.FloatField()),
+ ('repetition', models.PositiveIntegerField()),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('workout', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='workouts.workout')),
+ ],
+ ),
+ ]
diff --git a/FitnessTracker/workouts/migrations/0002_alter_workout_exercise.py b/FitnessTracker/workouts/migrations/0002_alter_workout_exercise.py
new file mode 100644
index 0000000..e2876ab
--- /dev/null
+++ b/FitnessTracker/workouts/migrations/0002_alter_workout_exercise.py
@@ -0,0 +1,20 @@
+# Generated by Django 5.1.2 on 2024-12-01 00:19
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('exercises', '0001_initial'),
+ ('workouts', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='workout',
+ name='exercise',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='exercises.exercise'),
+ ),
+ ]
diff --git a/FitnessTracker/workouts/migrations/0003_check.py b/FitnessTracker/workouts/migrations/0003_check.py
new file mode 100644
index 0000000..3937a32
--- /dev/null
+++ b/FitnessTracker/workouts/migrations/0003_check.py
@@ -0,0 +1,22 @@
+# Generated by Django 5.1.2 on 2024-12-02 10:45
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('workouts', '0002_alter_workout_exercise'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Check',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('set', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='workouts.set')),
+ ],
+ ),
+ ]
diff --git a/FitnessTracker/workouts/migrations/0004_done_delete_check.py b/FitnessTracker/workouts/migrations/0004_done_delete_check.py
new file mode 100644
index 0000000..d740def
--- /dev/null
+++ b/FitnessTracker/workouts/migrations/0004_done_delete_check.py
@@ -0,0 +1,25 @@
+# Generated by Django 5.1.2 on 2024-12-02 12:44
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('workouts', '0003_check'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Done',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('set', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='workout_set', to='workouts.set')),
+ ],
+ ),
+ migrations.DeleteModel(
+ name='Check',
+ ),
+ ]
diff --git a/FitnessTracker/workouts/migrations/0005_alter_done_set.py b/FitnessTracker/workouts/migrations/0005_alter_done_set.py
new file mode 100644
index 0000000..4ee2085
--- /dev/null
+++ b/FitnessTracker/workouts/migrations/0005_alter_done_set.py
@@ -0,0 +1,19 @@
+# Generated by Django 5.1.2 on 2024-12-02 15:08
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('workouts', '0004_done_delete_check'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='done',
+ name='set',
+ field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='workouts.set'),
+ ),
+ ]
diff --git a/FitnessTracker/workouts/migrations/0006_workout_start_time.py b/FitnessTracker/workouts/migrations/0006_workout_start_time.py
new file mode 100644
index 0000000..b933481
--- /dev/null
+++ b/FitnessTracker/workouts/migrations/0006_workout_start_time.py
@@ -0,0 +1,19 @@
+# Generated by Django 5.1.2 on 2024-12-03 06:58
+
+import django.utils.timezone
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('workouts', '0005_alter_done_set'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='workout',
+ name='start_time',
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ ]
diff --git a/FitnessTracker/workouts/migrations/__init__.py b/FitnessTracker/workouts/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/FitnessTracker/workouts/models.py b/FitnessTracker/workouts/models.py
new file mode 100644
index 0000000..5254e91
--- /dev/null
+++ b/FitnessTracker/workouts/models.py
@@ -0,0 +1,32 @@
+from django.db import models
+from django.contrib.auth.models import User
+from exercises.models import Exercise
+from routines.models import Routine
+from django.utils import timezone
+
+class Workout(models.Model):
+ routine = models.ForeignKey(Routine, on_delete=models.CASCADE)
+ exercise = models.ForeignKey(Exercise, on_delete=models.CASCADE)
+ note = models.TextField(blank=True)
+ restTime = models.PositiveIntegerField(blank=True)
+ start_time = models.DateTimeField(default=timezone.now)
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ def __str__(self) -> str:
+ return f'{self.routine.name}-{self.exercise.name}'
+
+
+class Set(models.Model):
+ weight = models.FloatField()
+ repetition = models.PositiveIntegerField()
+ workout = models.ForeignKey(Workout, on_delete=models.CASCADE)
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ def __str__(self) -> str:
+ return f'{self.workout.routine.name}-{self.workout.exercise.name} set: {self.id}'
+
+
+class Done(models.Model):
+
+ set = models.OneToOneField(Set, on_delete=models.CASCADE)
+ created_at = models.DateTimeField(auto_now_add=True)
\ No newline at end of file
diff --git a/FitnessTracker/workouts/templates/workouts/set_new.html b/FitnessTracker/workouts/templates/workouts/set_new.html
new file mode 100644
index 0000000..e69de29
diff --git a/FitnessTracker/workouts/templates/workouts/update_workout.html b/FitnessTracker/workouts/templates/workouts/update_workout.html
new file mode 100644
index 0000000..cab8971
--- /dev/null
+++ b/FitnessTracker/workouts/templates/workouts/update_workout.html
@@ -0,0 +1,61 @@
+{% extends 'main/base.html' %}
+
+{% block title %} Update Workout {% endblock %}
+
+{% block content %}
+
+{% load static %}
+
+
+
+
+
+
+ {% csrf_token %}
+
+
+
Routine: {{ routine.name }}
+
+
+
Update {{ workout.exercise.name }} Exercise
+
+
+
Select Exercise To Workout On
+
+
+ {{ workout.exercise.name }}
+
+
+
+
+
+
+ Workout Note (optional)
+ {{ workout.note }}
+
+
+
+ Rest Time (in seconds)
+
+
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/FitnessTracker/workouts/templates/workouts/workouts_list.html b/FitnessTracker/workouts/templates/workouts/workouts_list.html
new file mode 100644
index 0000000..b7d0b2b
--- /dev/null
+++ b/FitnessTracker/workouts/templates/workouts/workouts_list.html
@@ -0,0 +1,250 @@
+
+
+
+ {% for workout in workouts%}
+
+
+
+
+
+
+
({{workout.exercise.get_equipment_category_display}})
+
+
+
+
Rest Time: {{workout.restTime}}s
+
+
+
+
+ {% if request.user == routine.user %}
+ {% if workout.note %}
+
Notes:
+
+ {{workout.note}}
+
+
+ {% endif %}
+
+
+
+
+ {% csrf_token %}
+
+ Finsh Workout
+
+
+
+
+
+
+ {% endif %}
+
+
+
+ {% endfor %}
+
+ {% if request.user == routine.user %}
+
+
+
+
+
+
+ {% csrf_token %}
+
+
+
Add new workout to {{routine.name}}
+
+
+
+
+ Select Exercise to Workout on
+
+
+
+
+ Select Exercise
+
+
+
+
+
+
+
+
+
+
+ Workout Note (optional)
+
+
+
+
+
+
+
+
+ Rest Time (in seconds)
+
+
+
+
+
+
+
+
+ First Workout Set
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% csrf_token %}
+
+ Delete Routine
+
+
+
+
+
+
+ {% endif %}
+
+
\ No newline at end of file
diff --git a/FitnessTracker/workouts/tests.py b/FitnessTracker/workouts/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/FitnessTracker/workouts/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/FitnessTracker/workouts/urls.py b/FitnessTracker/workouts/urls.py
new file mode 100644
index 0000000..d990cfe
--- /dev/null
+++ b/FitnessTracker/workouts/urls.py
@@ -0,0 +1,17 @@
+from django.urls import path
+from . import views
+
+
+app_name = "workouts"
+
+urlpatterns = [
+ path("new/workout/", views.new_workout_view, name="new_workout_view"),
+ path("update//", views.update_workout_view, name="update_workout_view"),
+ path("delete//", views.delete_workout_view, name="delete_workout_view"),
+
+ path("sets/new/", views.new_set_view, name="new_set_view"),
+ path("sets/update//", views.update_set_view, name="update_set_view"),
+ path("sets/delete//", views.delete_set_view, name="delete_set_view"),
+ path("checks/add//", views.done_set_view, name="done_set_view"),
+ path("checks/finsh//", views.finish_workout_view, name="finish_workout_view"),
+]
\ No newline at end of file
diff --git a/FitnessTracker/workouts/views.py b/FitnessTracker/workouts/views.py
new file mode 100644
index 0000000..88d948f
--- /dev/null
+++ b/FitnessTracker/workouts/views.py
@@ -0,0 +1,216 @@
+from django.shortcuts import render, redirect
+from django.http import HttpRequest, HttpResponse
+
+from .forms import WorkoutForm
+from .models import Workout, Set, Done
+
+from routines.models import Routine
+
+from exercises.models import Exercise
+
+from django.contrib import messages
+
+
+def new_workout_view(request: HttpRequest, routine_id: int):
+ routine = Routine.objects.get(pk=routine_id)
+
+ workout_form = WorkoutForm()
+
+ if request.method == "POST":
+ workout_form = WorkoutForm(request.POST)
+ if workout_form.is_valid():
+ workout = workout_form.save(commit=False)
+ if request.POST["restTime"].isdigit():
+ workout.restTime = int(request.POST["restTime"])
+ workout.routine = routine
+ workout.save()
+ if request.POST.get("repetition", "").strip():
+ if request.POST['repetition'].isdigit():
+ weight = request.POST["weight"]
+ try:
+ weight = float(weight)
+ if weight <= 0:
+ messages.error(request, "Weight must be a positive number.", "alert-danger")
+ else:
+ new_set = Set(weight=weight, repetition=int(request.POST['repetition']), workout=workout)
+ new_set.save()
+ messages.success(request, "Workout added successfully!", "alert-success")
+
+ except ValueError:
+ messages.error(request, "Weight must be a valid number.", "alert-danger")
+ else:
+ messages.error(request, "Repetition must be digit number. Please correct the errors.", "alert-danger")
+ else:
+ messages.error(request, "Repetition must be digit number. Please correct the errors.", "alert-danger")
+ else:
+ messages.error(request, "Rest Time must be digit number. Please correct the errors.", "alert-danger")
+ else:
+ messages.error(request, "There is an error in the form. Please correct the errors.", "alert-danger")
+
+ else:
+ messages.error(request, "There are no data received. Please correct the errors.", "alert-danger")
+
+ return redirect("routines:routine_detail_view", routine_id=routine_id)
+
+
+def update_workout_view(request:HttpRequest, workout_id:int):
+ workout = Workout.objects.get(pk=workout_id)
+ routine = Routine.objects.get(pk=workout.routine.id)
+ exercises = Exercise.objects.all()
+
+ if request.method == "POST":
+ if request.POST["restTime"]:
+ if request.POST["restTime"].isdigit():
+ workout.restTime = int(request.POST["restTime"])
+ workout.note = request.POST.get("note", "").strip()
+ updated_exercise = Exercise.objects.get(pk=int(request.POST['exercise']))
+ workout.exercise = updated_exercise
+ workout.save()
+ messages.success(request, "Updated Workout Successfuly!", "alert-success")
+ return redirect("routines:routine_detail_view", routine_id=workout.routine.id)
+ else:
+ messages.error(request, "Rest Time must be digit number. Please correct the errors.", "alert-danger")
+
+ else:
+ messages.error(request, "There are no data received. Please correct the errors.", "alert-danger")
+
+ return render(
+ request,"workouts/update_workout.html",
+ {
+ "routine": routine,
+ 'workout':workout,
+ 'exercises': exercises,
+ },
+ )
+
+
+def delete_workout_view(request:HttpRequest, workout_id:int):
+ try:
+ workout = Workout.objects.get(pk=workout_id)
+ workout.delete()
+ messages.success(request, "Deleted Workout Successfuly!", "alert-success")
+ except Exception as e:
+ print(e)
+ messages.error(request, "Couldn't Delete Workout!", "alert-danger")
+
+ return redirect("main:home_view")
+
+
+def new_set_view(request:HttpRequest, workout_id:int):
+
+ if request.method == "POST":
+ workout = Workout.objects.get(pk=workout_id)
+
+ if request.POST['repetition'].isdigit():
+ weight = request.POST["weight"]
+ try:
+ weight = float(weight)
+ if weight <= 0:
+ messages.error(request, "Weight must be a positive number.", "alert-danger")
+ else:
+ new_set = Set(weight=weight, repetition=int(request.POST['repetition']), workout=workout)
+ new_set.save()
+ messages.success(request, "Added Set Successfuly!", "alert-success")
+ except ValueError:
+ messages.error(request, "Weight must be a valid number.", "alert-danger")
+ else:
+ messages.error(request, "Couldn't Add Set! Please insert correct numbers.", "alert-danger")
+
+ else:
+ messages.error(request, "Couldn't Add Set!. Please correct the errors.", "alert-danger")
+
+ return redirect("routines:routine_detail_view", routine_id=workout.routine.id)
+
+
+
+
+def update_set_view(request, set_id):
+ if request.method == "POST":
+ set_obj = Set.objects.get(pk=set_id)
+
+ if request.POST['repetition'].isdigit():
+ weight = request.POST["weight"]
+ try:
+ weight = float(weight)
+ if weight <= 0:
+ messages.error(request, "Weight must be a positive number.", "alert-danger")
+ else:
+ set_obj.weight = weight
+ set_obj.repetition = request.POST['repetition']
+ set_obj.save()
+ messages.success(request, "The set updated successfully!", "alert-success")
+ except ValueError:
+ messages.error(request, "Weight must be a valid number.", "alert-danger")
+ else:
+ messages.error(request, "Repetition must be a digit.", "alert-danger")
+
+ else:
+ messages.error(request, "Couldn't Update Set!. Please correct the errors.", "alert-danger")
+
+ return redirect("routines:routine_detail_view", routine_id=set_obj.workout.routine.id)
+
+
+def delete_set_view(request:HttpRequest, set_id:int):
+ try:
+ set = Set.objects.get(pk=set_id)
+ set.delete()
+ messages.success(request, f"{set.workout.exercise.name} set {set.id} Deleted Successfuly!", "alert-success")
+ except Exception as e:
+ print(e)
+ messages.error(request, "Couldn't Delete Set!", "alert-danger")
+
+ return redirect("routines:routine_detail_view", routine_id=set.workout.routine.id)
+
+
+
+def done_set_view(request: HttpRequest, set_id:int):
+ workout_set = Set.objects.get(pk=set_id)
+ try:
+
+ done = Done.objects.filter(set=workout_set).first()
+ if not done:
+ new_done = Done(set=workout_set)
+ new_done.save()
+ messages.success(request, "Set Done!", "alert-success")
+ else:
+ done.delete()
+ messages.error(request, "Set Undone!", "alert-danger")
+
+ except Set.DoesNotExist:
+ messages.error(request, "Set not found.")
+ except Exception as e:
+ messages.error(request, "An error occurred: " + str(e))
+
+ return redirect("routines:routine_detail_view", routine_id=workout_set.workout.routine.id)
+
+
+
+def finish_workout_view(request: HttpRequest, workout_id: int):
+ try:
+ workout = Workout.objects.get(pk=workout_id)
+ sets = workout.set_set.all()
+
+ for set_obj in sets:
+ done = Done.objects.filter(set=set_obj).first()
+ if done:
+ done.delete()
+
+ messages.success(request, f"All sets have been reset to 'Undone' for {workout.exercise.name} workout.", "alert-success")
+
+ except Workout.DoesNotExist:
+ messages.error(request, "Workout not found.", "alert-danger")
+ except Exception as e:
+ messages.error(request, f"An error occurred: {e}", "alert-danger")
+
+ return redirect("routines:routine_detail_view", routine_id=workout.routine.id)
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index cb428d7..341c55b 100644
--- a/README.md
+++ b/README.md
@@ -1,135 +1,47 @@
-# UNIT-PROJECT-3
+# Fitness Tracker for Gym
-## Create a Project of your own choosing
+## Overview
+The **Fitness Tracker** is a Django-based web application designed to help gym enthusiasts log and manage their fitness routines. This system simplifies workout tracking by offering features for creating reusable workout templates, logging workout sessions, and organizing exercises with detailed instructions.
-Base on what you’ve learned until now , create a project of your choosing. Impress us with your creativity and execution.
+## Key Features
-## Minimum Requirements
-- Use Django.
-- Use Templates & Template Inheritance.
-- Use static, media & dynamic urls as needed.
-- Organize your project in apps as needed.
-- Use models to represent you data.
-- Use a CSS library to style your website.
-- User Authentication & Authorization (register, login, logout, Limit access to some pages using permissions , etc.)
-- Use naming conventions & best practices.
-- Strive to make the user journey intuitive and complete.
+### 1. Routine Management
+- **Routine**: A reusable workout template that users can create for workouts they plan to perform repeatedly.
+- Routines are a framework for organizing related workouts and streamlining future workout sessions.
+### 2. **Workout Tracking**
+- **Workout**: Represents a specific instance of exercise activity that a user is currently performing.
+- Each workout can consist of multiple sets, and each set includes:
+ - **Weight**: The weight to be used for the exercise.
+ - **Repetition**: The number of times the user will perform the exercise with the specified weight.
+- Workouts are based on **Exercises**, such as Chest Press Machine, Triceps Pushdown Machine, or Lat Pulldown.
+- Each exercise comes with detailed instructions to guide users on the correct technique and form.
-## Example Projects :
+## User Roles and Permissions
+### 1. **Staff Users**
+- Manage the exercise database:
+ - Add, update, and delete exercises.
+ - Provide detailed instructions steps for each exercise.
-1. **Task Management System:**
-- **Overview:** Create a platform for managing tasks and projects within a team or organization.
-- **Features:**
-- User authentication and role-based access control.
-- Task creation, assignment, and tracking.
-- Project management with milestones.
-- File uploads and comments on tasks.
-- Notification system for task updates.
+### 2. **Regular Users**
+- **Routine Management**:
+ - Create, update, and delete routines.
+ - View their personal routines on a dedicated page, accessible only to the user.
+- **Workout Management**:
+ - Add workouts to routines.
+ - Edit or update workout details, including sets, weights, and repetitions.
+- **Exercise Access**:
+ - Browse the available exercises on a public page.
+ - View detailed instructions for exercises but it requires user registration.
-## Use python-dotenv to save your sensitive data.
-- https://pypi.org/project/python-dotenv/
+## Additional Features
+- **Public Routines**: A list of routines that all users can explore and benefit from them.
-## Use a CDN or cloud storage provider to sore your large static files (videos, images, etc.), such as:
-- https://firebase.google.com/docs/storage
-## Use Git & Github to manage and track changes in your project.
-- At lease commit and sync the changes once at the end of everyday.
+## Use Case
+- **For Gym Enthusiasts**: Track weightlifting sessions, create routines, and follow guided instructions for exercises.
-## Edit the README.md file to include (include the info at the top):
-- Project Name
-- Project Description
-- Features list.
-
-
-**Online Learning Platform:**
-
-- **Overview:** Develop a platform for online courses, quizzes, and educational resources.
-- **Features:**
-- User registration and profile management.
-- Course creation and enrollment.
-- Quiz and assessment functionalities.
-- Progress tracking and certificates.
-
-
-
-
-**Crowdfunding Platform:**
-
-- **Overview:** Build a crowdfunding website where users can create campaigns and seek financial support for their projects.
-- **Features:**
- - User profiles with project history.
- - Campaign creation and customization.
- - Payment integration for contributions.
- - Progress tracking and updates.
-
-**Job Board and Recruitment System:**
-
-- **Overview:** Develop a platform for job seekers and employers to connect.
-- **Features:**
- - User profiles with resumes.
- - Job posting and application functionalities.
- - Search and filter options for jobs.
- - Employer dashboards for managing postings.
-
-
-**Inventory Management System:**
-
-- **Overview:** Build a system for tracking and managing inventory for businesses.
-- **Features:**
- - User authentication with roles (e.g., admin, staff).
- - Product catalog with stock levels.
- - Order processing and tracking.
- - Reporting and analytics.
-
-
-**Recipe Sharing Platform:**
-
-- **Overview:** Create a platform where users can share and discover recipes.
-- **Features:**
- - User accounts with saved recipes.
- - Recipe creation and editing.
- - Search and categorization of recipes.
- - User ratings and reviews.
-
-## Resources:
-
-**Free high quality images :**
-
-- https://www.pexels.com/
-- https://unsplash.com
-
-**Free sounds website:**
-
-- https://mixkit.co/
-
-**Free stock videos:**
-
-- https://pixabay.com/videos/
-
-**Free Fonts:**
-
-- https://fonts.google.com
-
-**Free Icons**
-
-- https://fonts.google.com/icons
-- https://icons.getbootstrap.com/
-
-**CSS Library:**
-
-- https://getbootstrap.com/
-- https://get.foundation/index.html
-
-**CSS Animation libraries:**
-
-- https://animate.style
-- https://www.minimamente.com/project/magic/
-
-
-
-