diff --git a/.gitignore b/.gitignore
index bdd4074d7d98ff4c226296bfaf9fd16a18e1283d..959c40e9c80563d38398a7c47fb79f24c54ecbf8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ backend/secfit/.vscode/
 backend/secfit/*/migrations/__pycache__/
 backend/secfit/*/__pycache__/
 backend/secfit/db.sqlite3
+venv/
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 960342d75f9ec243d7ba801dd24e0570a1e0144e..b5bb6adac444779c99188739679a4d8586717fed 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,25 +1,26 @@
 stages:
     - test
     - staging
-  
+
 test:
-  image: python:3.8
-  stage: test
-  script:
-  # this configures Django application to use attached postgres database that is run on `postgres` host
-    - cd backend/secfit
-    - apt-get update -qy
-    - pip install -r requirements.txt
+    image: python:3.8
+    stage: test
+    script:
+        # this configures Django application to use attached postgres database that is run on `postgres` host
+        - cd backend/secfit
+        - apt-get update -qy
+        - pip install -r requirements.txt
+        - python manage.py test
 
 staging:
-  type: deploy
-  image: ruby
-  stage: staging
-  script:
-    - apt-get update -qy
-    - apt-get install -y ruby-dev
-    - gem install dpl
-    - dpl --provider=heroku --app=<your-frontend-app-name> --api-key=$HEROKU_STAGING_API_KEY
-    - dpl --provider=heroku --app=<your-backend-app-name> --api-key=$HEROKU_STAGING_API_KEY
-  only:
-    - master
+    type: deploy
+    image: ruby
+    stage: staging
+    script:
+        - apt-get update -qy
+        - apt-get install -y ruby-dev
+        - gem install dpl
+        - dpl --provider=heroku --app=tdt4242-frontend --api-key=$HEROKU_STAGING_API_KEY
+        - dpl --provider=heroku --app=tdt4242-backend --api-key=$HEROKU_STAGING_API_KEY
+    only:
+        - master
diff --git a/backend/secfit/.coverage b/backend/secfit/.coverage
new file mode 100644
index 0000000000000000000000000000000000000000..ce5050a7db5d6dd8f0dac60db75d9bc448a84a1d
Binary files /dev/null and b/backend/secfit/.coverage differ
diff --git a/backend/secfit/.coveragerc b/backend/secfit/.coveragerc
new file mode 100644
index 0000000000000000000000000000000000000000..a852abd2d0c861a680f3921ead12cbe30a2e310f
--- /dev/null
+++ b/backend/secfit/.coveragerc
@@ -0,0 +1,3 @@
+# .coveragerc
+[report]
+show_missing = True
diff --git a/backend/secfit/challenges/__init__.py b/backend/secfit/challenges/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/backend/secfit/challenges/admin.py b/backend/secfit/challenges/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..654de84c13ee0ed30bdfaeada17ae89b1fcc60c2
--- /dev/null
+++ b/backend/secfit/challenges/admin.py
@@ -0,0 +1,5 @@
+from django.contrib import admin
+from challenges.models import Challenge
+
+# Register your models here.
+admin.site.register(Challenge)
diff --git a/backend/secfit/challenges/apps.py b/backend/secfit/challenges/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..621ad57def4152f467f9ef369522aaad2d7e0824
--- /dev/null
+++ b/backend/secfit/challenges/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class ChallengesConfig(AppConfig):
+    name = 'challenges'
diff --git a/backend/secfit/challenges/migrations/0001_initial.py b/backend/secfit/challenges/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..75bed3d8e8e6dfb659d3a64f2fd55ccc0591059a
--- /dev/null
+++ b/backend/secfit/challenges/migrations/0001_initial.py
@@ -0,0 +1,50 @@
+# Generated by Django 3.1 on 2022-03-12 15:29
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('groups', '0004_group_athletes'),
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Challenge',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('title', models.CharField(default='Challenge', max_length=24)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='ChallengeCompleted',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('completed', models.BooleanField(default=False)),
+                ('Challenge', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='challenges.challenge')),
+                ('athlete', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+                ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='groups.group')),
+            ],
+        ),
+        migrations.AddField(
+            model_name='challenge',
+            name='athletes',
+            field=models.ManyToManyField(related_name='_challenge_athletes_+', through='challenges.ChallengeCompleted', to=settings.AUTH_USER_MODEL),
+        ),
+        migrations.AddField(
+            model_name='challenge',
+            name='coach',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL),
+        ),
+        migrations.AddField(
+            model_name='challenge',
+            name='groups',
+            field=models.ManyToManyField(through='challenges.ChallengeCompleted', to='groups.Group'),
+        ),
+    ]
diff --git a/backend/secfit/challenges/migrations/0002_auto_20220312_1532.py b/backend/secfit/challenges/migrations/0002_auto_20220312_1532.py
new file mode 100644
index 0000000000000000000000000000000000000000..5f276e94392f3d6ffc7e79f77447ad11e1dd9f08
--- /dev/null
+++ b/backend/secfit/challenges/migrations/0002_auto_20220312_1532.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.1 on 2022-03-12 15:32
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('challenges', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='challengecompleted',
+            old_name='Challenge',
+            new_name='challenge',
+        ),
+    ]
diff --git a/backend/secfit/challenges/migrations/0003_auto_20220313_1647.py b/backend/secfit/challenges/migrations/0003_auto_20220313_1647.py
new file mode 100644
index 0000000000000000000000000000000000000000..6c87bca4c4bbfaa4e1fb01362b6af0a111b70e7b
--- /dev/null
+++ b/backend/secfit/challenges/migrations/0003_auto_20220313_1647.py
@@ -0,0 +1,21 @@
+# Generated by Django 3.1 on 2022-03-13 16:47
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('challenges', '0002_auto_20220312_1532'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='challenge',
+            name='groups',
+        ),
+        migrations.RemoveField(
+            model_name='challengecompleted',
+            name='group',
+        ),
+    ]
diff --git a/backend/secfit/challenges/migrations/0004_auto_20220313_1942.py b/backend/secfit/challenges/migrations/0004_auto_20220313_1942.py
new file mode 100644
index 0000000000000000000000000000000000000000..94a272906bdf353d4d5788ef7b6e26d2d1d2ad8f
--- /dev/null
+++ b/backend/secfit/challenges/migrations/0004_auto_20220313_1942.py
@@ -0,0 +1,33 @@
+# Generated by Django 3.1 on 2022-03-13 19:42
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('challenges', '0003_auto_20220313_1647'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='challenge',
+            name='athletes',
+        ),
+        migrations.AddField(
+            model_name='challenge',
+            name='athlete',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
+        ),
+        migrations.AddField(
+            model_name='challenge',
+            name='completed',
+            field=models.BooleanField(default=False),
+        ),
+        migrations.DeleteModel(
+            name='ChallengeCompleted',
+        ),
+    ]
diff --git a/backend/secfit/challenges/migrations/__init__.py b/backend/secfit/challenges/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/backend/secfit/challenges/models.py b/backend/secfit/challenges/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..be9e44c94ed2586d3ff7708a9090d269a59b3f4a
--- /dev/null
+++ b/backend/secfit/challenges/models.py
@@ -0,0 +1,20 @@
+from django.db import models
+from django.contrib.auth import get_user_model
+
+
+# Create your models here.
+class Challenge(models.Model):
+    """Django model for a group of athletes
+
+    Each group has a coach
+
+    Attributes:
+        coach:       Who coaches the group
+        title:       Name of group
+        athletes     All athletes that are assigned to challenge
+    """
+    title = models.CharField(max_length=24, default="Challenge")
+    coach = models.ForeignKey(
+        get_user_model(), on_delete=models.CASCADE, related_name="+")
+    athlete = models.ForeignKey(get_user_model(), on_delete=models.CASCADE,  blank=True, null=True)
+    completed = models.BooleanField(default=False)
diff --git a/backend/secfit/challenges/serializers.py b/backend/secfit/challenges/serializers.py
new file mode 100644
index 0000000000000000000000000000000000000000..64fa8e082390fff4fb03126bc6cd1e1f5b08d552
--- /dev/null
+++ b/backend/secfit/challenges/serializers.py
@@ -0,0 +1,27 @@
+from rest_framework import serializers
+from challenges.models import Challenge
+
+class ChallengeSerializer(serializers.HyperlinkedModelSerializer):
+    class Meta:
+        model = Challenge
+        fields = [
+            "title",
+            "athlete",
+            "coach",
+            "completed"
+        ]
+    
+    def create(self, validated_data):
+        return Challenge.objects.create(**validated_data)
+
+
+
+class ChallengeGetSerializer(serializers.HyperlinkedModelSerializer):
+    class Meta:
+        model = Challenge
+        fields = [
+            "title",
+            "athlete",
+            "coach",
+            "completed"
+        ]
\ No newline at end of file
diff --git a/backend/secfit/challenges/tests.py b/backend/secfit/challenges/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6
--- /dev/null
+++ b/backend/secfit/challenges/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/backend/secfit/challenges/urls.py b/backend/secfit/challenges/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..296979cfd18df66a89bb9cba556e96866935ba88
--- /dev/null
+++ b/backend/secfit/challenges/urls.py
@@ -0,0 +1,7 @@
+from django.urls import path
+from challenges import views
+
+urlpatterns = [
+    path("api/challenges/", views.ChallengeList.as_view(), name="challenge-list"),
+    path("api/challenges/<int:pk>/", views.ChallengeDetail.as_view(), name="challenge-detail"),
+]
diff --git a/backend/secfit/challenges/views.py b/backend/secfit/challenges/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..98f5a0d84df4a4f8a8fc61f1edd68d24bd80f448
--- /dev/null
+++ b/backend/secfit/challenges/views.py
@@ -0,0 +1,44 @@
+from rest_framework import mixins, generics
+from challenges.models import Challenge
+from challenges.serializers import ChallengeSerializer, ChallengeGetSerializer
+
+# Create your views here
+
+class ChallengeList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
+    serializer_class = ChallengeSerializer
+
+
+    def get(self, request, *args, **kwargs):
+        self.serializer_class = ChallengeGetSerializer
+        return self.list(request, *args, **kwargs)
+
+    def post(self, request, *args, **kwargs):
+        return self.create(request, *args, **kwargs)
+
+    def get_queryset(self):
+        # Tror man filtrerer her
+        return Challenge.objects.all()
+
+
+class ChallengeDetail(
+    mixins.RetrieveModelMixin,
+    mixins.UpdateModelMixin,
+    mixins.DestroyModelMixin,
+    generics.GenericAPIView,
+):
+    lookup_field_options = ["pk"]
+    # serializer_class = UserSerializer
+    # queryset = get_user_model().objects.all()
+    # permission_classes = [permissions.IsAuthenticated & (IsCurrentUser | IsReadOnly)]
+
+    def get_object(self):
+        for field in self.lookup_field_options:
+            if field in self.kwargs:
+                self.lookup_field = field
+                break
+
+        return super().get_object()
+
+    def get(self, request, *args, **kwargs):
+        #self.serializer_class = UserGetSerializer
+        return self.retrieve(request, *args, **kwargs)
diff --git a/backend/secfit/comments/permissions.py b/backend/secfit/comments/permissions.py
index b863cc7782ff295afe3ca1b2cc95519c70eb48eb..8ef3d7b1cc6bff9200254103c8efd5e710d103f5 100644
--- a/backend/secfit/comments/permissions.py
+++ b/backend/secfit/comments/permissions.py
@@ -11,7 +11,7 @@ class IsCommentVisibleToUser(permissions.BasePermission):
     - The comment is on a workout owned by the user
     """
 
-    def has_object_permission(self, request, view, obj):
+    def has_object_permission(self, request, obj):
         # Write permissions are only allowed to the owner.
         return (
             obj.workout.visibility == "PU"
diff --git a/backend/secfit/comments/views.py b/backend/secfit/comments/views.py
index b74d0f208c9bcf06ee49817541d47742767f0b7d..5256fb2078a1cd3814aca2f755cd7c5c9e44dd82 100644
--- a/backend/secfit/comments/views.py
+++ b/backend/secfit/comments/views.py
@@ -1,4 +1,3 @@
-from django.shortcuts import render
 from rest_framework import generics, mixins
 from comments.models import Comment, Like
 from rest_framework import permissions
@@ -12,7 +11,6 @@ from rest_framework.filters import OrderingFilter
 class CommentList(
     mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView
 ):
-    # queryset = Comment.objects.all()
     serializer_class = CommentSerializer
     permission_classes = [permissions.IsAuthenticated]
     filter_backends = [OrderingFilter]
@@ -40,29 +38,24 @@ class CommentList(
             - The comment is on a coach visibility workout and the user is the workout owner's coach
             - The comment is on a workout owned by the user
             """
-            # The code below is kind of duplicate of the one in ./permissions.py
-            # We should replace it with a better solution.
-            # Or maybe not.
-            
-            qs = Comment.objects.filter(
-                Q(workout__visibility="PU")
-                | Q(owner=self.request.user)
-                | (
-                    Q(workout__visibility="CO")
-                    & Q(workout__owner__coach=self.request.user)
-                )
-                | Q(workout__owner=self.request.user)
-            ).distinct()
+
+            qs =  Comment.objects.all()
+            for index in range(len(qs)):
+                    if not IsCommentVisibleToUser().has_object_permission(self.request, qs[index]):
+                        qs.remove(qs[index])
+
 
         return qs
 
-# Details of comment
 class CommentDetail(
     mixins.RetrieveModelMixin,
     mixins.UpdateModelMixin,
     mixins.DestroyModelMixin,
     generics.GenericAPIView,
 ):
+    '''Details of comment
+    
+    '''
     queryset = Comment.objects.all()
     serializer_class = CommentSerializer
     permission_classes = [
@@ -81,6 +74,10 @@ class CommentDetail(
 
 # List of likes
 class LikeList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
+    '''Details of likes
+    
+    '''
+
     serializer_class = LikeSerializer
     permission_classes = [permissions.IsAuthenticated]
 
@@ -97,13 +94,15 @@ class LikeList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericA
         return Like.objects.filter(owner=self.request.user)
 
 
-# Details of like
 class LikeDetail(
     mixins.RetrieveModelMixin,
     mixins.UpdateModelMixin,
     mixins.DestroyModelMixin,
     generics.GenericAPIView,
 ):
+    '''Details of like
+    
+    '''
     queryset = Like.objects.all()
     serializer_class = LikeSerializer
     permission_classes = [permissions.IsAuthenticated]
diff --git a/backend/secfit/groups/__init__.py b/backend/secfit/groups/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/backend/secfit/groups/admin.py b/backend/secfit/groups/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..164edd42892507c7fdfa60a8595d9e30ddd0950a
--- /dev/null
+++ b/backend/secfit/groups/admin.py
@@ -0,0 +1,6 @@
+from django.contrib import admin
+from .models import Group
+
+
+# Register your models here.
+admin.site.register(Group)
diff --git a/backend/secfit/groups/apps.py b/backend/secfit/groups/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..e6984a81f64b2c3bf2ebae54e2d9501333bafa38
--- /dev/null
+++ b/backend/secfit/groups/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class GroupsConfig(AppConfig):
+    name = 'groups'
diff --git a/backend/secfit/groups/migrations/0001_initial.py b/backend/secfit/groups/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..95b55317937311dd07693dcecc0800f686ad87ff
--- /dev/null
+++ b/backend/secfit/groups/migrations/0001_initial.py
@@ -0,0 +1,26 @@
+# Generated by Django 3.1 on 2022-03-10 20:19
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Group',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(default='Group', max_length=24)),
+                ('athletes', models.ManyToManyField(to=settings.AUTH_USER_MODEL)),
+                ('coach', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='coached_groups', to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+    ]
diff --git a/backend/secfit/groups/migrations/0002_auto_20220310_2024.py b/backend/secfit/groups/migrations/0002_auto_20220310_2024.py
new file mode 100644
index 0000000000000000000000000000000000000000..f26b194c39e1568b3388ce874a2829a36d005bc1
--- /dev/null
+++ b/backend/secfit/groups/migrations/0002_auto_20220310_2024.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.1 on 2022-03-10 20:24
+
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('groups', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='group',
+            name='athletes',
+            field=models.ManyToManyField(blank=True, null=True, to=settings.AUTH_USER_MODEL),
+        ),
+    ]
diff --git a/backend/secfit/groups/migrations/0003_remove_group_athletes.py b/backend/secfit/groups/migrations/0003_remove_group_athletes.py
new file mode 100644
index 0000000000000000000000000000000000000000..62e72f26c17a6c23e3d5c60597bc6494ca52c462
--- /dev/null
+++ b/backend/secfit/groups/migrations/0003_remove_group_athletes.py
@@ -0,0 +1,17 @@
+# Generated by Django 3.1 on 2022-03-11 13:38
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('groups', '0002_auto_20220310_2024'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='group',
+            name='athletes',
+        ),
+    ]
diff --git a/backend/secfit/groups/migrations/0004_group_athletes.py b/backend/secfit/groups/migrations/0004_group_athletes.py
new file mode 100644
index 0000000000000000000000000000000000000000..fee7653e4499366fbf20642de12386dc7f3cbd71
--- /dev/null
+++ b/backend/secfit/groups/migrations/0004_group_athletes.py
@@ -0,0 +1,20 @@
+# Generated by Django 3.1 on 2022-03-11 14:03
+
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('groups', '0003_remove_group_athletes'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='group',
+            name='athletes',
+            field=models.ManyToManyField(to=settings.AUTH_USER_MODEL),
+        ),
+    ]
diff --git a/backend/secfit/groups/migrations/__init__.py b/backend/secfit/groups/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/backend/secfit/groups/models.py b/backend/secfit/groups/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..80786867c0873aef9c5e3f52a205f59f3ac3682f
--- /dev/null
+++ b/backend/secfit/groups/models.py
@@ -0,0 +1,18 @@
+from curses.ascii import US
+from django.db import models
+from django.contrib.auth import get_user_model
+
+# Create your models here.
+class Group(models.Model):
+    """Django model for a group of athletes
+
+    Each group has a coach
+
+    Attributes:
+        coach:       Who coaches the group
+        name:        Name of group
+    """
+    coach = models.ForeignKey(
+        "users.User", on_delete=models.CASCADE, related_name="coached_groups")
+    name = models.CharField(max_length=24, default="Group")
+    athletes = models.ManyToManyField(get_user_model())
\ No newline at end of file
diff --git a/backend/secfit/groups/serializers.py b/backend/secfit/groups/serializers.py
new file mode 100644
index 0000000000000000000000000000000000000000..f468e2e2d58eb432d22e5faf4194af7fa59f39fc
--- /dev/null
+++ b/backend/secfit/groups/serializers.py
@@ -0,0 +1,33 @@
+from rest_framework import serializers
+from groups.models import Group
+from django.contrib.auth import get_user_model
+
+class GroupSerializer(serializers.HyperlinkedModelSerializer):
+    class Meta:
+        model = Group
+        fields = [
+            "name",
+            "athletes",
+            "coach",
+        ]
+    
+    def create(self, validated_data):
+
+        files_data = []
+        if "athletes" in validated_data:
+            files_data = validated_data.pop("athletes")
+
+        group_obj = Group.objects.create(**validated_data)
+        group_obj.athletes.set(files_data)
+
+        return group_obj
+
+
+class GroupGetSerializer(serializers.HyperlinkedModelSerializer):
+    class Meta:
+        model = Group
+        fields = [
+            "name",
+            "athletes",
+            "coach",
+        ]
\ No newline at end of file
diff --git a/backend/secfit/groups/tests.py b/backend/secfit/groups/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6
--- /dev/null
+++ b/backend/secfit/groups/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/backend/secfit/groups/urls.py b/backend/secfit/groups/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..43e705113b764a7a883638dcd890a5b0df3ec2c9
--- /dev/null
+++ b/backend/secfit/groups/urls.py
@@ -0,0 +1,7 @@
+from django.urls import path
+from groups import views
+
+urlpatterns = [
+    path("api/groups/", views.GroupList.as_view(), name="group-list"),
+    path("api/groups/<int:pk>/", views.GroupDetail.as_view(), name="group-detail"),
+]
diff --git a/backend/secfit/groups/views.py b/backend/secfit/groups/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..09a5a610e02fc77b8d086a9cfa817c0eae6dc1b7
--- /dev/null
+++ b/backend/secfit/groups/views.py
@@ -0,0 +1,49 @@
+from rest_framework import mixins, generics
+from groups.models import Group
+from groups.serializers import GroupSerializer, GroupGetSerializer
+# Create your views here
+
+class GroupList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
+    serializer_class = GroupSerializer
+
+
+    def get(self, request, *args, **kwargs):
+        self.serializer_class = GroupGetSerializer
+        return self.list(request, *args, **kwargs)
+
+    def post(self, request, *args, **kwargs):
+        return self.create(request, *args, **kwargs)
+
+    def get_queryset(self):
+        # Tror man filtrerer her
+        qs = Group.objects.all()
+
+        if self.request.user:
+            # Return the currently logged in user
+            # status = self.request.query_params.get("user", None)
+            qs = Group.objects.filter(coach=self.request.user.pk)
+
+        return qs
+
+class GroupDetail(
+    mixins.RetrieveModelMixin,
+    mixins.UpdateModelMixin,
+    mixins.DestroyModelMixin,
+    generics.GenericAPIView,
+):
+    lookup_field_options = ["pk"]
+    # serializer_class = UserSerializer
+    # queryset = get_user_model().objects.all()
+    # permission_classes = [permissions.IsAuthenticated & (IsCurrentUser | IsReadOnly)]
+
+    def get_object(self):
+        for field in self.lookup_field_options:
+            if field in self.kwargs:
+                self.lookup_field = field
+                break
+
+        return super().get_object()
+
+    def get(self, request, *args, **kwargs):
+        #self.serializer_class = UserGetSerializer
+        return self.retrieve(request, *args, **kwargs)
diff --git a/backend/secfit/requirements.txt b/backend/secfit/requirements.txt
index 9feb375bde1e8fb7befe6c102dd29beeee7c6940..0fc007f159667113fad281604f4927f3c616a406 100644
Binary files a/backend/secfit/requirements.txt and b/backend/secfit/requirements.txt differ
diff --git a/backend/secfit/secfit/settings.py b/backend/secfit/secfit/settings.py
index 96dbdcde7b3df80ade7cb75f08ebb31353b918c7..705e53b7d6226269ced4fef460e93bb861bfd088 100644
--- a/backend/secfit/secfit/settings.py
+++ b/backend/secfit/secfit/settings.py
@@ -61,8 +61,11 @@ INSTALLED_APPS = [
     "workouts.apps.WorkoutsConfig",
     "meals.apps.MealsConfig",
     "users.apps.UsersConfig",
+    "groups.apps.GroupsConfig",
+    "challenges.apps.ChallengesConfig",
     "comments.apps.CommentsConfig",
     "corsheaders",
+    'django_nose',
 ]
 
 MIDDLEWARE = [
@@ -158,3 +161,28 @@ REST_FRAMEWORK = {
 AUTH_USER_MODEL = "users.User"
 
 DEBUG = True
+
+# Django Logging Information
+LOGGING = {
+    "version": 1,
+    "disable_existing_loggers": False,
+    "handlers": {
+        "console": {"class": "logging.StreamHandler"},
+    },
+    "loggers": {
+        "django": {
+            "handlers": ["console"],
+            "level": "INFO",
+        },
+    }
+}
+
+# Use nose to run all tests
+TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
+
+# Tell nose to measure coverage on the 'foo' and 'bar' apps
+NOSE_ARGS = [
+    '--with-coverage',
+    '--cover-package=workouts.permissions, users.serializers',
+]
+
diff --git a/backend/secfit/secfit/urls.py b/backend/secfit/secfit/urls.py
index 93fc1902dfcf8c10bce7f680b763d92756d6eae9..9ca3400bc544a897078218ce132582079dce9ffc 100644
--- a/backend/secfit/secfit/urls.py
+++ b/backend/secfit/secfit/urls.py
@@ -22,6 +22,8 @@ urlpatterns = [
     path("admin/", admin.site.urls),
     path("", include("workouts.urls")),
     path("", include("meals.urls")),
+    path("", include("groups.urls")),
+    path("", include("challenges.urls")),
 ]
 
 urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
diff --git a/backend/secfit/users/admin.py b/backend/secfit/users/admin.py
index fc0af23c4473e29bcc06045aebfdd0d21989d22d..9d8f7d0ffeb2311ecf7725f81985738be096b166 100644
--- a/backend/secfit/users/admin.py
+++ b/backend/secfit/users/admin.py
@@ -11,7 +11,6 @@ class CustomUserAdmin(UserAdmin):
     add_form = CustomUserCreationForm
     form = CustomUserChangeForm
     model = get_user_model()
-    # list_display = UserAdmin.list_display + ('coach',)
     fieldsets = UserAdmin.fieldsets + ((None, {"fields": ("coach",)}),)
     add_fieldsets = UserAdmin.add_fieldsets + ((None, {"fields": ("coach",)}),)
 
diff --git a/backend/secfit/users/migrations/0010_group_groupmembership.py b/backend/secfit/users/migrations/0010_group_groupmembership.py
new file mode 100644
index 0000000000000000000000000000000000000000..53287d3bbd1d77cf8358f2153c843a093d7d52c3
--- /dev/null
+++ b/backend/secfit/users/migrations/0010_group_groupmembership.py
@@ -0,0 +1,30 @@
+# Generated by Django 3.1 on 2022-03-07 12:02
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('users', '0009_auto_20210204_1055'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Group',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('coach', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='coached_groups', to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='GroupMembership',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('athlete', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='joined_groups', to=settings.AUTH_USER_MODEL)),
+                ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='athletes', to='users.group')),
+            ],
+        ),
+    ]
diff --git a/backend/secfit/users/migrations/0011_auto_20220310_1949.py b/backend/secfit/users/migrations/0011_auto_20220310_1949.py
new file mode 100644
index 0000000000000000000000000000000000000000..d94ecf33264072c0f37633acad0d9754cbe85ed1
--- /dev/null
+++ b/backend/secfit/users/migrations/0011_auto_20220310_1949.py
@@ -0,0 +1,30 @@
+# Generated by Django 3.1 on 2022-03-10 19:49
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('users', '0010_group_groupmembership'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='group',
+            name='coach',
+        ),
+        migrations.AddField(
+            model_name='group',
+            name='name',
+            field=models.CharField(default='Group', max_length=24),
+        ),
+        migrations.AlterField(
+            model_name='user',
+            name='groups',
+            field=models.ManyToManyField(to='users.Group'),
+        ),
+        migrations.DeleteModel(
+            name='GroupMembership',
+        ),
+    ]
diff --git a/backend/secfit/users/migrations/0012_auto_20220310_2019.py b/backend/secfit/users/migrations/0012_auto_20220310_2019.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ea0d8835753a9b9a91a6008fbf689e87b4826d5
--- /dev/null
+++ b/backend/secfit/users/migrations/0012_auto_20220310_2019.py
@@ -0,0 +1,22 @@
+# Generated by Django 3.1 on 2022-03-10 20:19
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('groups', '0001_initial'),
+        ('users', '0011_auto_20220310_1949'),
+    ]
+
+    operations = [
+        migrations.DeleteModel(
+            name='Group',
+        ),
+        migrations.AlterField(
+            model_name='user',
+            name='groups',
+            field=models.ManyToManyField(to='groups.Group'),
+        ),
+    ]
diff --git a/backend/secfit/users/migrations/0013_auto_20220310_2024.py b/backend/secfit/users/migrations/0013_auto_20220310_2024.py
new file mode 100644
index 0000000000000000000000000000000000000000..21f645b9b1b1901b8b53395e30b6131720640a6d
--- /dev/null
+++ b/backend/secfit/users/migrations/0013_auto_20220310_2024.py
@@ -0,0 +1,19 @@
+# Generated by Django 3.1 on 2022-03-10 20:24
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('groups', '0002_auto_20220310_2024'),
+        ('users', '0012_auto_20220310_2019'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='user',
+            name='groups',
+            field=models.ManyToManyField(blank=True, null=True, to='groups.Group'),
+        ),
+    ]
diff --git a/backend/secfit/users/migrations/0014_auto_20220311_1403.py b/backend/secfit/users/migrations/0014_auto_20220311_1403.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b4b0034cad60efe8d3a8d962d1df305bc3f20d7
--- /dev/null
+++ b/backend/secfit/users/migrations/0014_auto_20220311_1403.py
@@ -0,0 +1,19 @@
+# Generated by Django 3.1 on 2022-03-11 14:03
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('auth', '0012_alter_user_first_name_max_length'),
+        ('users', '0013_auto_20220310_2024'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='user',
+            name='groups',
+            field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups'),
+        ),
+    ]
diff --git a/backend/secfit/users/models.py b/backend/secfit/users/models.py
index d48528655225b97a32833eb1a6629e0d266ba6df..c261d859d1c753b6a90e3fba26271923955386f9 100644
--- a/backend/secfit/users/models.py
+++ b/backend/secfit/users/models.py
@@ -2,10 +2,8 @@ from django.db import models
 from django.contrib.auth.models import AbstractUser
 from django.contrib.auth import get_user_model
 
-
 # Create your models here.
 
-
 class User(AbstractUser):
     """
     Standard Django User model with an added field for a user's coach.
@@ -73,4 +71,4 @@ class Offer(models.Model):
     )
 
     status = models.CharField(max_length=8, choices=STATUS_CHOICES, default=PENDING)
-    timestamp = models.DateTimeField(auto_now_add=True)
+    timestamp = models.DateTimeField(auto_now_add=True)
\ No newline at end of file
diff --git a/backend/secfit/users/tests.py b/backend/secfit/users/tests.py
index 7ce503c2dd97ba78597f6ff6e4393132753573f6..34560ebb7094d495bda786cfade12f9cb822892e 100644
--- a/backend/secfit/users/tests.py
+++ b/backend/secfit/users/tests.py
@@ -1,3 +1,42 @@
 from django.test import TestCase
 
+from users.serializers import UserSerializer, UserPutSerializer, AthleteFileSerializer
+from users.models import User
+
 # Create your tests here.
+
+
+class UserTestCase(TestCase):
+    def setUp(self):
+        # Animal.objects.create(name="lion", sound="roar")
+        # Animal.objects.create(name="cat", sound="meow")
+        # get_user_model().objects.create()
+        self.user_data = {
+            "username": "NikoTest",
+            "password": "",
+            "email": "nikolaidokken",
+            "phone_number": "12345678",
+            "country": "Norway",
+            "city": "Baerum",
+            "street_address": "Veien 2"
+            }
+        self.testUser = User.objects.create(username="Niko", password="niko")
+        
+    def test_user_is_created(self):
+        """User is correctly created"""
+        newUser = UserSerializer().create(validated_data=self.user_data)
+        foundUser = User.objects.get(username="NikoTest")
+        self.assertEqual(newUser, foundUser)
+
+    def test_user_valid_password(self):
+        serializer = UserSerializer(data=self.user_data)
+        self.assertEqual(serializer.validate_password(True), True)
+
+    def test_user_put_athletes(self):
+        newAthlete = User.objects.create(username="athlete", password="athlete")
+        UserPutSerializer().update(self.testUser, {"athletes": [newAthlete]})
+        
+        self.assertEqual(self.testUser.athletes.get(), newAthlete)
+
+    def test_create_athlete_file(self):
+        AthleteFileSerializer().create({"athlete": self.testUser, "owner_id": self.testUser.id})
diff --git a/backend/secfit/users/urls.py b/backend/secfit/users/urls.py
index 507c27008e8b0997e486945a27bfe3afc55d89de..2d22bf70408aed78d70be76910d1b15485166e5f 100644
--- a/backend/secfit/users/urls.py
+++ b/backend/secfit/users/urls.py
@@ -1,6 +1,6 @@
-from django.urls import path, include
+from django.urls import path
 from users import views
-from rest_framework.urlpatterns import format_suffix_patterns
+
 
 urlpatterns = [
     path("api/users/", views.UserList.as_view(), name="user-list"),
diff --git a/backend/secfit/users/views.py b/backend/secfit/users/views.py
index f5efef5c2ce82566ab380cecad344e3143c31813..7e8f4b56f15eea7b21a06a176dff9a566566d308 100644
--- a/backend/secfit/users/views.py
+++ b/backend/secfit/users/views.py
@@ -1,4 +1,3 @@
-import django
 from rest_framework import mixins, generics
 from workouts.mixins import CreateListModelMixin
 from rest_framework import permissions
@@ -10,15 +9,11 @@ from users.serializers import (
     UserGetSerializer,
 )
 from rest_framework.permissions import (
-    AllowAny,
-    IsAdminUser,
-    IsAuthenticated,
     IsAuthenticatedOrReadOnly,
 )
 from users.models import Offer, AthleteFile
 from django.contrib.auth import get_user_model
 from django.db.models import Q
-from django.shortcuts import get_object_or_404
 from rest_framework.parsers import MultiPartParser, FormParser
 from users.permissions import IsCurrentUser, IsAthlete, IsCoach
 from workouts.permissions import IsOwner, IsReadOnly
@@ -98,7 +93,6 @@ class OfferList(
         serializer.save(owner=self.request.user)
 
     def get_queryset(self):
-        qs = Offer.objects.none()
         result = Offer.objects.none()
 
         if self.request.user:
@@ -106,22 +100,20 @@ class OfferList(
                 Q(owner=self.request.user) | Q(recipient=self.request.user)
             ).distinct()
             qp = self.request.query_params
-            u = self.request.user
+            user = self.request.user
 
-            # filtering by status (if provided)
-            s = qp.get("status", None)
-            if s is not None and self.request is not None:
-                qs = qs.filter(status=s)
+            status = qp.get("status", None)
+            if status is not None and self.request is not None:
+                qs = qs.filter(status=status)
                 if qp.get("status", None) is None:
-                    qs = Offer.objects.filter(Q(owner=u)).distinct()
-
-            # filtering by category (sent or received)
-            c = qp.get("category", None)
-            if c is not None and qp is not None:
-                if c == "sent":
-                    qs = qs.filter(owner=u)
-                elif c == "received":
-                    qs = qs.filter(recipient=u)
+                    qs = Offer.objects.filter(Q(owner=user)).distinct()
+
+            category = qp.get("category", None)
+            if category is not None and qp is not None:
+                if category == "sent":
+                    qs = qs.filter(owner=user)
+                elif category == "received":
+                    qs = qs.filter(recipient=user)
             return qs
         else:
             return result
diff --git a/backend/secfit/workouts/models.py b/backend/secfit/workouts/models.py
index 0f6214f2d9919d17fe68e93416a183a52209bda6..44c88cbbf370d9c0645e46268f2e38b236cb296e 100644
--- a/backend/secfit/workouts/models.py
+++ b/backend/secfit/workouts/models.py
@@ -2,29 +2,9 @@
 log workouts (Workout), which contain instances (ExerciseInstance) of various
 type of exercises (Exercise). The user can also upload files (WorkoutFile) .
 """
-import os
 from django.db import models
-from django.core.files.storage import FileSystemStorage
-from django.conf import settings
 from django.contrib.auth import get_user_model
 
-
-class OverwriteStorage(FileSystemStorage):
-    """Filesystem storage for overwriting files. Currently unused."""
-
-    def get_available_name(self, name, max_length=None):
-        """https://djangosnippets.org/snippets/976/
-        Returns a filename that's free on the target storage system, and
-        available for new content to be written to.
-
-        Args:
-            name (str): Name of the file
-            max_length (int, optional): Maximum length of a file name. Defaults to None.
-        """
-        if self.exists(name):
-            os.remove(os.path.join(settings.MEDIA_ROOT, name))
-
-
 # Create your models here.
 class Workout(models.Model):
     """Django model for a workout that users can log.
diff --git a/backend/secfit/workouts/serializers.py b/backend/secfit/workouts/serializers.py
index 6abbe31ffd71c5e9cbba140e34a03176b127a4bf..eb9b497b3f71a1822a40fc600625140ef9976108 100644
--- a/backend/secfit/workouts/serializers.py
+++ b/backend/secfit/workouts/serializers.py
@@ -109,33 +109,20 @@ class WorkoutSerializer(serializers.HyperlinkedModelSerializer):
 
         return workout
 
-    def update(self, instance, validated_data):
-        """Custom logic for updating a Workout with its ExerciseInstances and Workouts.
-
-        This is needed because each object in both exercise_instances and files must be iterated
-        over and handled individually.
-
+    def update_exercises(self, validated_data, instance):
+        """Custom logic and helper function for updating a Workout with its ExerciseInstances.
+        This updates existing exercise instances without adding or deleting object.
+        zip() will yield n 2-tuples, where n is min(len(exercise_instance), len(exercise_instance_data))
+        
         Args:
-            instance (Workout): Current Workout object
             validated_data: Contains data for validated fields
+            instance (Workout): Current Workout object
 
-        Returns:
-            Workout: Updated Workout instance
         """
+
         exercise_instances_data = validated_data.pop("exercise_instances")
         exercise_instances = instance.exercise_instances
 
-        instance.name = validated_data.get("name", instance.name)
-        instance.notes = validated_data.get("notes", instance.notes)
-        instance.visibility = validated_data.get("visibility", instance.visibility)
-        instance.date = validated_data.get("date", instance.date)
-        instance.save()
-
-        # Handle ExerciseInstances
-
-        # This updates existing exercise instances without adding or deleting object.
-        # zip() will yield n 2-tuples, where n is
-        # min(len(exercise_instance), len(exercise_instance_data))
         for exercise_instance, exercise_instance_data in zip(
             exercise_instances.all(), exercise_instances_data
         ):
@@ -162,7 +149,15 @@ class WorkoutSerializer(serializers.HyperlinkedModelSerializer):
             for i in range(len(exercise_instances_data), len(exercise_instances.all())):
                 exercise_instances.all()[i].delete()
 
-        # Handle WorkoutFiles
+
+    def update_files(self, validated_data, instance):
+        """Custom logic and helper function for updating a Workout with its WorkoutFiles.
+        
+        Args:
+            validated_data: Contains data for validated fields
+            instance (Workout): Current Workout object
+
+        """
 
         if "files" in validated_data:
             files_data = validated_data.pop("files")
@@ -184,6 +179,31 @@ class WorkoutSerializer(serializers.HyperlinkedModelSerializer):
                 for i in range(len(files_data), len(files.all())):
                     files.all()[i].delete()
 
+    def update(self, instance, validated_data):
+        """Custom logic for updating a Workout with its ExerciseInstances and Workouts.
+
+        This is needed because each object in both exercise_instances and files must be iterated
+        over and handled individually.
+
+        Args:
+            instance (Workout): Current Workout object
+            validated_data: Contains data for validated fields
+
+        Returns:
+            Workout: Updated Workout instance
+        """
+       
+
+        instance.name = validated_data.get("name", instance.name)
+        instance.notes = validated_data.get("notes", instance.notes)
+        instance.visibility = validated_data.get("visibility", instance.visibility)
+        instance.date = validated_data.get("date", instance.date)
+        instance.save()
+
+        
+        self.update_exercises(validated_data, instance)
+        self.update_files(validated_data, instance)
+
         return instance
 
     def get_owner_username(self, obj):
diff --git a/backend/secfit/workouts/tests.py b/backend/secfit/workouts/tests.py
index 7fbbf7847f5b0f201d408d4017cc865d614e2615..e1c514554ef80001e41222e78b2fe12776c507f6 100644
--- a/backend/secfit/workouts/tests.py
+++ b/backend/secfit/workouts/tests.py
@@ -2,5 +2,99 @@
 Tests for the workouts application.
 """
 from django.test import TestCase
+from workouts.permissions import IsOwner, IsOwnerOfWorkout, IsCoachAndVisibleToCoach, IsCoachOfWorkoutAndVisibleToCoach, IsPublic, IsWorkoutPublic, IsReadOnly
+from workouts.models import Workout
+from users.models import User
+from django.utils import timezone
+
+
+class Obj():
+    def __init__(self, owner, workout=None, visibility="PU"):
+        self.owner = owner
+        self.workout = workout
+        self.visibility = visibility
+
+class Request():
+    def __init__(self, user, data, method="GET"):
+        self.user = user
+        self.method = method
+        self.data = data
 
 # Create your tests here.
+class WorkoutTestCase(TestCase):
+    def setUp(self):
+        coach = User.objects.create(username="Coach", password="coach")
+        user = User.objects.create(username="Niko", password="niko", coach=coach)
+        otherUser = User.objects.create(username="Ian", password="ian")
+        
+        workout = Workout.objects.create(date=timezone.now(), owner_id=user.id, visibility="PU")
+        workoutUrl = "http://noe:8000/api/workouts/" + str(workout.id) + "/"
+        
+        self.request = Request(user=user, data={"workout": workoutUrl})
+        self.requestWithoutValidWorkout = Request(user=user, data={"test": None})
+        self.otherRequest = Request(user=otherUser, data={"workout": workoutUrl})
+        self.coachRequest = Request(user=coach, data={"workout": workoutUrl})
+        self.obj = Obj(owner=user, workout=workout)
+        
+    def test_is_owner(self):
+        """User is correctly created"""
+        has_permission = IsOwner().has_object_permission(request=self.request,view={}, obj=self.obj)
+        self.assertEqual(has_permission, True)
+
+    def test_is_owner_of_workout(self):
+        self.request.method = "POST"
+        has_permission = IsOwnerOfWorkout().has_permission(self.request, {})
+        self.assertEqual(has_permission, True)
+        
+        self.otherRequest.method = "POST"
+        has_permission = IsOwnerOfWorkout().has_permission(self.otherRequest, {})
+        self.assertEqual(has_permission, False)
+        
+        self.request.method = "GET"
+        has_permission = IsOwnerOfWorkout().has_permission(self.request, {})
+        self.assertEqual(has_permission, True)
+
+        has_permission = IsOwnerOfWorkout().has_object_permission(self.request, {}, self.obj)
+        self.assertEqual(has_permission, True)
+
+        self.requestWithoutValidWorkout.method = "POST"
+        has_permission = IsOwnerOfWorkout().has_permission(self.requestWithoutValidWorkout, {})
+        self.assertEqual(has_permission, False)
+    
+    def test_is_coach_and_visible_to_coach(self):
+        has_permission = IsCoachAndVisibleToCoach().has_object_permission(self.request, {}, self.obj)
+        self.assertEqual(has_permission, False)
+        
+        has_permission = IsCoachAndVisibleToCoach().has_object_permission(self.coachRequest, {}, self.obj)
+        self.assertEqual(has_permission, True)
+
+    def test_is_coach_of_workout_and_visible_to_coach(self):
+        has_permission = IsCoachOfWorkoutAndVisibleToCoach().has_object_permission(self.coachRequest, {}, self.obj)
+        self.assertEqual(has_permission, True)
+        
+        has_permission = IsCoachOfWorkoutAndVisibleToCoach().has_object_permission(self.request, {}, self.obj)
+        self.assertEqual(has_permission, False)
+
+    def test_is_public(self):
+        is_public = IsPublic().has_object_permission(self.request, {}, self.obj)
+        self.assertEqual(is_public, True)
+        
+        self.obj.visibility = "PR"
+        is_public = IsPublic().has_object_permission(self.request, {}, self.obj)
+        self.assertEqual(is_public, False)
+    
+    def test_is_workout_public(self):        
+        is_public = IsWorkoutPublic().has_object_permission(self.request, {}, self.obj)
+        self.assertEqual(is_public, True)
+       
+        self.obj.workout.visibility = "PR"
+        is_public = IsWorkoutPublic().has_object_permission(self.request, {}, self.obj)
+        self.assertEqual(is_public, False)
+
+    def test_is_read_only(self):
+        is_read_only = IsReadOnly().has_object_permission(self.request, {}, self.obj)
+        self.assertEqual(is_read_only, True)
+        
+        self.request.method = "POST"
+        is_read_only = IsReadOnly().has_object_permission(self.request, {}, self.obj)
+        self.assertEqual(is_read_only, False)
diff --git a/backend/secfit/workouts/views.py b/backend/secfit/workouts/views.py
index efddf40454376b23d233f9fe2cecaf9da43fddb8..590e49d5576c6cf50c79faaec52ceb9e8246ea52 100644
--- a/backend/secfit/workouts/views.py
+++ b/backend/secfit/workouts/views.py
@@ -54,13 +54,15 @@ def api_root(request, format=None):
     )
 
 
-# Allow users to save a persistent session in their browser
 class RememberMe(
     mixins.ListModelMixin,
     mixins.CreateModelMixin,
     mixins.DestroyModelMixin,
     generics.GenericAPIView,
 ):
+    """Allow users to save a persistent session in their browser
+
+    """
 
     serializer_class = RememberMeSerializer
 
@@ -71,10 +73,10 @@ class RememberMe(
             return Response({"remember_me": self.rememberme()})
 
     def post(self, request):
-        cookieObject = namedtuple("Cookies", request.COOKIES.keys())(
+        cookie_object = namedtuple("Cookies", request.COOKIES.keys())(
             *request.COOKIES.values()
         )
-        user = self.get_user(cookieObject)
+        user = self.get_user(cookie_object)
         refresh = RefreshToken.for_user(user)
         return Response(
             {
@@ -83,8 +85,8 @@ class RememberMe(
             }
         )
 
-    def get_user(self, cookieObject):
-        decode = base64.b64decode(cookieObject.remember_me)
+    def get_user(self, cookie_object):
+        decode = base64.b64decode(cookie_object.remember_me)
         user, sign = pickle.loads(decode)
 
         # Validate signature
diff --git a/frontend/www/index.html b/frontend/www/index.html
index bf98ea0f0c0ae222bfc8a58927361a99f513473c..f2b44849bca482114208c6fff30d3028b3bccce5 100644
--- a/frontend/www/index.html
+++ b/frontend/www/index.html
@@ -1,13 +1,21 @@
 <!DOCTYPE html>
 <html lang="en">
     <head>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0">
+        <meta charset="UTF-8" />
+        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
         <title>Home</title>
-        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
+        <link
+            href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css"
+            rel="stylesheet"
+            integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1"
+            crossorigin="anonymous"
+        />
 
-        <script src="https://kit.fontawesome.com/0ce6c392ca.js" crossorigin="anonymous"></script>
-        <link rel="stylesheet" href="styles/style.css">
+        <script
+            src="https://kit.fontawesome.com/0ce6c392ca.js"
+            crossorigin="anonymous"
+        ></script>
+        <link rel="stylesheet" href="styles/style.css" />
         <script src="scripts/navbar.js" type="text/javascript" defer></script>
     </head>
     <body>
@@ -17,17 +25,27 @@
             <div class="row mt-3">
                 <div class="col-lg text-center">
                     <h2 class="mt-3">Welcome to SecFit!</h2>
-                    <p>SecFit (coming from "SuperSecure" and "Fitness") is the most secure fitness logging app on the net.
-                        You can conveniently log a workout using either our website or our app. You can also view and comment on others'
+                    <p>
+                        SecFit (coming from "SuperSecure" and "Fitness") is the
+                        most secure fitness logging app on the net. You can
+                        conveniently log a workout using either our website or
+                        our app. You can also view and comment on others'
                         workouts!
                     </p>
-                    <img src="img/fitness.jpg" class="img-fluid" alt="DUMBBELLS">
+                    <img
+                        src="img/fitness.jpg"
+                        class="img-fluid"
+                        alt="DUMBBELLS"
+                    />
                 </div>
             </div>
         </div>
 
         <script src="scripts/scripts.js"></script>
-        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
+        <script
+            src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js"
+            integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW"
+            crossorigin="anonymous"
+        ></script>
     </body>
-
-</html>
\ No newline at end of file
+</html>
diff --git a/frontend/www/myathletes.html b/frontend/www/myathletes.html
index 5c92ea7accd91493df3263116e77f0c4a06c4f6d..1f862d8d3c575c4d648dd677407fa65c47d8f074 100644
--- a/frontend/www/myathletes.html
+++ b/frontend/www/myathletes.html
@@ -42,10 +42,27 @@
                 <div class="list-group" id="list-tab" role="tablist"></div>
             </div>
             <div class="col-lg-4">
-              <div class="tab-content" id="nav-tabContent"></div>
+                <div class="tab-content" id="nav-tabContent"></div>
             </div>
             <div class="col-lg-6"></div>
-          </div>
+        </div>
+        <form id="form-group" class="row">
+            <div id="list-groups-div" class="col-lg-6">
+                <label for="group-control" class="form-label mt-2">Groups</label>
+                <div id="group-control"></div>
+                <input class="form-control" name="group" type="text" placeholder="Group name: username1, username2" id="group-input" />
+                <input type="button" class="btn btn-primary mt-1 mb-2" id="button-submit-group" value="Submit"> 
+            </div>
+        </form>
+        <form id="form-challenge" class="row">
+            <div id="list-challenges-div" class="col-lg-6">
+                <label for="challenge-control" class="form-label mt-2">Challenges</label>
+                <div id="challenge-control"></div>
+                <input class="form-control" name="challenge-title" type="text" placeholder="Run 5 km" id="challenge-title-input" />
+                <input class="form-control" name="challenge-user" type="text" placeholder="username1, username2" id="challenge-user-input" />
+                <input type="button" class="btn btn-primary mt-1 mb-2" id="button-submit-challenge" value="Submit"> 
+            </div>
+        </form>
     </div>
 
     <template id="template-filled-athlete">
@@ -57,6 +74,13 @@
             </span>
         </div>
     </template>
+    
+    <template id="template-filled-group">
+        <div class="card mb-2">
+            <div class="card-body" id="card-body">
+            </div>
+        </div>
+    </template>
 
     <template id="template-empty-athlete">
         <div class="entry input-group">
@@ -68,6 +92,16 @@
         </div>
     </template>
 
+    <template id="template-empty-group">
+        <div class="entry input-group">
+            <input class="form-control" name="group" type="text" placeholder="Comma-separated usernames"/>
+                <button class="btn btn-success btn-add" type="button">
+                    <i class="fas fa-plus"></i>
+                </button>
+            </span>
+        </div>
+    </template>
+
     <template id="template-athlete-tab">
         <a class="list-group-item list-group-item-action" data-bs-toggle="list" href="#list-tab" role="tab"></a>
     </template>
@@ -90,6 +124,9 @@
     <script src="scripts/defaults.js"></script>
     <script src="scripts/scripts.js"></script>
     <script src="scripts/myathletes.js"></script>
+    <script src="scripts/groups.js"></script>
+    <script src="scripts/group.js"></script>
+    <script src="scripts/challenge.js"></script>
     <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
 </body>
 </html>
\ No newline at end of file
diff --git a/frontend/www/profile.html b/frontend/www/profile.html
new file mode 100644
index 0000000000000000000000000000000000000000..876f14cadff51234b7c43bd9627dc181afc85bea
--- /dev/null
+++ b/frontend/www/profile.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <meta charset="UTF-8" />
+        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+        <title>Profile</title>
+        <link
+            href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css"
+            rel="stylesheet"
+            integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1"
+            crossorigin="anonymous"
+        />
+
+        <script
+            src="https://kit.fontawesome.com/0ce6c392ca.js"
+            crossorigin="anonymous"
+        ></script>
+        <link rel="stylesheet" href="styles/style.css" />
+        <script src="scripts/navbar.js" type="text/javascript" defer></script>
+    </head>
+    <body>
+        <navbar-el></navbar-el>
+
+        <div class="container">
+            <div class="row mt-3">
+                <h2 class="mt-3">My Profile</h2>
+                <form class="row g-3" id="form-user">
+                    <div class="col-lg-6 ">
+                        <label for="inputName" class="form-label">Name</label>
+                        <input type="text" class="form-control" id="inputName" name="username" readonly>
+                    </div>
+                    <div class="col-lg-6">
+                        <label for="inputMail" class="form-label">E-mail</label>
+                        <input type="text" class="form-control" id="inputMail" name="email" readonly>
+                    </div>
+                    <div class="col-lg-6">
+                        <label for="inputPhone" class="form-label">Phone</label>
+                        <input type="text" class="form-control" id="inputPhone" name="phone_number" readonly></input>
+                    </div>
+                    <div class="col-lg-6">
+                        <label for="inputAddress" class="form-label">Address</label>
+                        <input type="text" class="form-control" id="inputAddress" name="address" readonly></input>
+                    </div>
+                    <label for="inputAddress" class="form-label fs-5">Challenges</label>
+                    
+                    <div class="col-lg-4">
+                            <div id="challenge-control"></div>
+                        
+                    </div>
+                    <div class="col-lg-4">
+                        
+                    </div>
+                    <div class="col-lg-6"></div>
+                    <div class="col-lg-6"></div>
+                    <div class="col-lg-6"></div>
+                    </div>
+                </form>
+            </div>
+        </div>
+        <template id="template-challenge-joined">
+            <div class="col-lg-4">
+                <div class="card mb-2" style="width: 18rem;">
+                    <div class="card-body">
+                        <h5 class="card-title"></h5>
+                        <p id="card-body-completed"></p>
+                    </div>
+                </div>
+            </div>
+        </template>
+
+        <script src="scripts/defaults.js"></script>
+        <script src="scripts/scripts.js"></script>
+        <script src="scripts/profile.js"></script>
+        <script src="scripts/challenges.js"></script>
+        <script
+            src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js"
+            integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW"
+            crossorigin="anonymous"
+        ></script>
+    </body>
+</html>
diff --git a/frontend/www/scripts/challenge.js b/frontend/www/scripts/challenge.js
new file mode 100644
index 0000000000000000000000000000000000000000..5d04ed2c904b01151a1d2290fde71287dc571aef
--- /dev/null
+++ b/frontend/www/scripts/challenge.js
@@ -0,0 +1,45 @@
+async function generateChallengeForm(athlete) {
+    let form = document.querySelector("#form-challenge");
+
+    let formData = new FormData(form);
+    let submitForm = new FormData();
+
+    let inputTitle = formData.get("challenge-title");
+    submitForm.append("title", inputTitle);
+
+    let response = await sendRequest("GET", `${HOST}/api/users/${athlete}/`);
+    let result = await response.json();
+    submitForm.append("athlete", result.url);
+
+    let currentUser = await getCurrentUser();
+    submitForm.append("coach", currentUser.url);
+    return submitForm;
+}
+
+async function createChallenge() {
+    let inputAthletes = document.querySelector("#challenge-user-input").value;
+    console.log(inputAthletes);
+    let athletes = inputAthletes.replaceAll(" ", "").split(",");
+
+    for (let athlete of athletes) {
+        let submitForm = await generateChallengeForm(athlete);
+        let response = await sendRequest(
+            "POST",
+            `${HOST}/api/challenges/`,
+            submitForm,
+            ""
+        );
+
+        if (response.ok) {
+            console.log("GUCCI");
+        } else {
+            let data = await response.json();
+            let alert = createAlert("Could not create new challenge!", data);
+            document.body.prepend(alert);
+        }
+    }
+    document.getElementById("challenge-title-input").value = "";
+    document.getElementById("challenge-user-input").value = "";
+    window.location.reload();
+    return;
+}
diff --git a/frontend/www/scripts/challenges.js b/frontend/www/scripts/challenges.js
new file mode 100644
index 0000000000000000000000000000000000000000..0920e508e4160f975fcb0033fb7801a1a44c3388
--- /dev/null
+++ b/frontend/www/scripts/challenges.js
@@ -0,0 +1,51 @@
+async function fetchChallenges() {
+    let response = await sendRequest("GET", `${HOST}/api/challenges/`);
+    let currentUser = await getCurrentUser();
+
+    if (!response.ok) {
+        throw new Error(`HTTP error! status: ${response.status}`);
+    } else {
+        let data = await response.json();
+
+        let challenges = data.results;
+        let container = document.getElementById("challenge-control");
+
+        let templateChallenge = document.querySelector(
+            "#template-challenge-joined"
+        );
+
+        challenges
+            .filter((challenge) => challenge.athlete == currentUser.url)
+            .forEach((challenge) => {
+                let cloneChallenge = templateChallenge.content.cloneNode(true);
+                let titleChallenge = cloneChallenge.querySelector("h5");
+                let statusChallenge = cloneChallenge.querySelector(
+                    "#card-body-completed"
+                );
+
+                // let h5 = titleChallenge.querySelector("h5");
+                titleChallenge.innerHTML = challenge.title;
+                statusChallenge.innerHTML = challenge.completed
+                    ? "Completed"
+                    : "Not Completed";
+
+                container.appendChild(cloneChallenge);
+            });
+        return challenges;
+    }
+}
+
+window.addEventListener("DOMContentLoaded", async () => {
+    await fetchChallenges();
+
+    // let buttonSetCoach = document.querySelector("#button-set-coach");
+    // let buttonEditCoach = document.querySelector("#button-edit-coach");
+    // let buttonCancelCoach = document.querySelector("#button-cancel-coach");
+
+    // buttonSetCoach.addEventListener(
+    //     "click",
+    //     async (event) => await setCoach(event)
+    // );
+    // buttonEditCoach.addEventListener("click", editCoach);
+    // buttonCancelCoach.addEventListener("click", cancelCoach);
+});
diff --git a/frontend/www/scripts/exercise.js b/frontend/www/scripts/exercise.js
index f845fe1844b633cf1b0bf1365eee4323c4c84bcc..81af9320ec690a67be3db463836eab1b8b00d006 100644
--- a/frontend/www/scripts/exercise.js
+++ b/frontend/www/scripts/exercise.js
@@ -4,36 +4,32 @@ let deleteButton;
 let editButton;
 let oldFormData;
 
-class MuscleGroup { 
-    constructor(type) {
-        this.isValidType = false;
-        this.validTypes = ["Legs", "Chest", "Back", "Arms", "Abdomen", "Shoulders"]
+class MuscleGroup {
+    isValidType = false;
+    validTypes = ["Legs", "Chest", "Back", "Arms", "Abdomen", "Shoulders"];
 
+    constructor(type) {
         this.type = this.validTypes.includes(type) ? type : undefined;
-    };
+    }
 
     setMuscleGroupType = (newType) => {
-        this.isValidType = false;
-        
-        if(this.validTypes.includes(newType)){
-            this.isValidType = true;
-            this.type = newType;
-        }
-        else{
+        if (!this.validTypes.includes(newType)) {
+            this.isValidType = false;
             alert("Invalid muscle group!");
+            return;
         }
-
+        this.isValidType = true;
+        this.type = newType;
     };
-    
+
     getMuscleGroupType = () => {
-        console.log(this.type, "SWIOEFIWEUFH")
         return this.type;
-    }
+    };
 }
 
-function handleCancelButtonDuringEdit() {
+function handleCancelButtonDuringEdit(e, afterUpdate = false) {
     setReadOnly(true, "#form-exercise");
-    document.querySelector("select").setAttribute("disabled", "")
+    document.querySelector("select").setAttribute("disabled", "");
     okButton.className += " hide";
     deleteButton.className += " hide";
     cancelButton.className += " hide";
@@ -41,21 +37,22 @@ function handleCancelButtonDuringEdit() {
 
     cancelButton.removeEventListener("click", handleCancelButtonDuringEdit);
 
-    let form = document.querySelector("#form-exercise");
-    if (oldFormData.has("name")) form.name.value = oldFormData.get("name");
-    if (oldFormData.has("description")) form.description.value = oldFormData.get("description");
-    if (oldFormData.has("duration")) form.duration.value = oldFormData.get("duration");
-    if (oldFormData.has("calories")) form.calories.value = oldFormData.get("calories");
-    if (oldFormData.has("muscleGroup")) form.muscleGroup.value = oldFormData.get("muscleGroup");
-    if (oldFormData.has("unit")) form.unit.value = oldFormData.get("unit");
-    
+    if (!afterUpdate) {
+        let form = document.querySelector("#form-exercise");
+        if (oldFormData.has("name")) form.name.value = oldFormData.get("name");
+        if (oldFormData.has("description")) form.description.value = oldFormData.get("description");
+        if (oldFormData.has("duration")) form.duration.value = oldFormData.get("duration");
+        if (oldFormData.has("calories")) form.calories.value = oldFormData.get("calories");
+        if (oldFormData.has("muscleGroup")) form.muscleGroup.value = oldFormData.get("muscleGroup");
+        if (oldFormData.has("unit")) form.unit.value = oldFormData.get("unit");
+    }
+
     oldFormData.delete("name");
     oldFormData.delete("description");
     oldFormData.delete("duration");
     oldFormData.delete("calories");
     oldFormData.delete("muscleGroup");
     oldFormData.delete("unit");
-
 }
 
 function handleCancelButtonDuringCreate() {
@@ -63,15 +60,17 @@ function handleCancelButtonDuringCreate() {
 }
 
 async function createExercise() {
-    document.querySelector("select").removeAttribute("disabled")
+    document.querySelector("select").removeAttribute("disabled");
     let form = document.querySelector("#form-exercise");
     let formData = new FormData(form);
-    let body = {"name": formData.get("name"), 
-                "description": formData.get("description"),
-                "duration": formData.get("duration"),
-                "calories": formData.get("calories"),
-                "muscleGroup": formData.get("muscleGroup"), 
-                "unit": formData.get("unit")};
+    let body = {
+        name: formData.get("name"),
+        description: formData.get("description"),
+        duration: formData.get("duration"),
+        calories: formData.get("calories"),
+        muscleGroup: formData.get("muscleGroup"),
+        unit: formData.get("unit"),
+    };
 
     let response = await sendRequest("POST", `${HOST}/api/exercises/`, body);
 
@@ -87,7 +86,7 @@ async function createExercise() {
 function handleEditExerciseButtonClick() {
     setReadOnly(false, "#form-exercise");
 
-    document.querySelector("select").removeAttribute("disabled")
+    document.querySelector("select").removeAttribute("disabled");
 
     editButton.className += " hide";
     okButton.className = okButton.className.replace(" hide", "");
@@ -114,26 +113,26 @@ async function deleteExercise(id) {
 async function retrieveExercise(id) {
     let response = await sendRequest("GET", `${HOST}/api/exercises/${id}/`);
 
-    console.log(response.ok)
+    console.log(response.ok);
 
     if (!response.ok) {
         let data = await response.json();
         let alert = createAlert("Could not retrieve exercise data!", data);
         document.body.prepend(alert);
     } else {
-        document.querySelector("select").removeAttribute("disabled")
+        document.querySelector("select").removeAttribute("disabled");
         let exerciseData = await response.json();
         let form = document.querySelector("#form-exercise");
         let formData = new FormData(form);
 
         for (let key of formData.keys()) {
-            let selector
-            key !== "muscleGroup" ? selector = `input[name="${key}"], textarea[name="${key}"]` : selector = `select[name=${key}]`
+            let selector =
+                key !== "muscleGroup" ? `input[name="${key}"], textarea[name="${key}"]` : `select[name=${key}]`;
             let input = form.querySelector(selector);
             let newVal = exerciseData[key];
             input.value = newVal;
         }
-        document.querySelector("select").setAttribute("disabled", "")
+        document.querySelector("select").setAttribute("disabled", "");
     }
 }
 
@@ -141,17 +140,19 @@ async function updateExercise(id) {
     let form = document.querySelector("#form-exercise");
     let formData = new FormData(form);
 
-    let muscleGroupSelector = document.querySelector("select")
-    muscleGroupSelector.removeAttribute("disabled")
+    let muscleGroupSelector = document.querySelector("select");
+    muscleGroupSelector.removeAttribute("disabled");
 
     let selectedMuscleGroup = new MuscleGroup(formData.get("muscleGroup"));
 
-    let body = {"name": formData.get("name"), 
-                "description": formData.get("description"),
-                "duration": formData.get("duration"),
-                "calories": formData.get("calories"),
-                "muscleGroup": selectedMuscleGroup.getMuscleGroupType(),
-                "unit": formData.get("unit")};
+    let body = {
+        name: formData.get("name"),
+        description: formData.get("description"),
+        duration: formData.get("duration"),
+        calories: formData.get("calories"),
+        muscleGroup: selectedMuscleGroup.getMuscleGroupType(),
+        unit: formData.get("unit"),
+    };
     let response = await sendRequest("PUT", `${HOST}/api/exercises/${id}/`, body);
 
     if (!response.ok) {
@@ -159,23 +160,7 @@ async function updateExercise(id) {
         let alert = createAlert(`Could not update exercise ${id}`, data);
         document.body.prepend(alert);
     } else {
-        muscleGroupSelector.setAttribute("disabled", "")
-        // duplicate code from handleCancelButtonDuringEdit
-        // you should refactor this
-        setReadOnly(true, "#form-exercise");
-        okButton.className += " hide";
-        deleteButton.className += " hide";
-        cancelButton.className += " hide";
-        editButton.className = editButton.className.replace(" hide", "");
-    
-        cancelButton.removeEventListener("click", handleCancelButtonDuringEdit);
-        
-        oldFormData.delete("name");
-        oldFormData.delete("description");
-        oldFormData.delete("duration");
-        oldFormData.delete("calories");
-        oldFormData.delete("muscleGroup");
-        oldFormData.delete("unit");
+        handleCancelButtonDuringEdit(true);
     }
 }
 
@@ -189,23 +174,22 @@ window.addEventListener("DOMContentLoaded", async () => {
     const urlParams = new URLSearchParams(window.location.search);
 
     // view/edit
-    if (urlParams.has('id')) {
-        const exerciseId = urlParams.get('id');
+    if (urlParams.has("id")) {
+        const exerciseId = urlParams.get("id");
         await retrieveExercise(exerciseId);
 
         editButton.addEventListener("click", handleEditExerciseButtonClick);
-        deleteButton.addEventListener("click", (async (id) => await deleteExercise(id)).bind(undefined, exerciseId));
-        okButton.addEventListener("click", (async (id) => await updateExercise(id)).bind(undefined, exerciseId));
-    } 
+        deleteButton.addEventListener("click", (async (id) => deleteExercise(id)).bind(undefined, exerciseId));
+        okButton.addEventListener("click", (async (id) => updateExercise(id)).bind(undefined, exerciseId));
+        return;
+    }
     //create
-    else {
-        setReadOnly(false, "#form-exercise");
+    setReadOnly(false, "#form-exercise");
 
-        editButton.className += " hide";
-        okButton.className = okButton.className.replace(" hide", "");
-        cancelButton.className = cancelButton.className.replace(" hide", "");
+    editButton.className += " hide";
+    okButton.className = okButton.className.replace(" hide", "");
+    cancelButton.className = cancelButton.className.replace(" hide", "");
 
-        okButton.addEventListener("click", async () => await createExercise());
-        cancelButton.addEventListener("click", handleCancelButtonDuringCreate);
-    }
-});
\ No newline at end of file
+    okButton.addEventListener("click", async () => createExercise());
+    cancelButton.addEventListener("click", handleCancelButtonDuringCreate);
+});
diff --git a/frontend/www/scripts/gallery.js b/frontend/www/scripts/gallery.js
index f9c07b449947470c8df29c8f51894758cf38c025..6ceb0878f9a8d9a00fadf30735cf52bd36019b48 100644
--- a/frontend/www/scripts/gallery.js
+++ b/frontend/www/scripts/gallery.js
@@ -1,105 +1,110 @@
 let goBackButton;
 let submitNewFileButton;
 
-async function retrieveWorkoutImages(id) {  
+async function retrieveWorkoutImages(id) {
     let workoutData = null;
     let response = await sendRequest("GET", `${HOST}/api/workouts/${id}/`);
     if (!response.ok) {
         let data = await response.json();
         let alert = createAlert("Could not retrieve workout data!", data);
         document.body.prepend(alert);
-    } else {
-        workoutData = await response.json();
+        return workoutData;
+    }
+    workoutData = await response.json();
 
-        document.getElementById("workout-title").innerHTML = "Workout name: " + workoutData["name"];
-        document.getElementById("workout-owner").innerHTML = "Owner: " + workoutData["owner_username"];
+    document.getElementById("workout-title").innerHTML = "Workout name: " + workoutData["name"];
+    document.getElementById("workout-owner").innerHTML = "Owner: " + workoutData["owner_username"];
 
-        let hasNoImages = workoutData.files.length == 0;
-        let noImageText = document.querySelector("#no-images-text");
+    let hasNoImages = workoutData.files.length == 0;
+    let noImageText = document.querySelector("#no-images-text");
 
-        if(hasNoImages){
-            noImageText.classList.remove("hide");
-            return;
-        }
+    if (hasNoImages) {
+        noImageText.classList.remove("hide");
+        return;
+    }
 
-        noImageText.classList.add("hide");
-
-        
-        let filesDiv = document.getElementById("img-collection");
-        let filesDeleteDiv = document.getElementById("img-collection-delete");
-        
-        const currentImageFileElement = document.querySelector("#current");
-        let isFirstImg = true;
-
-        let fileCounter = 0;
-
-        for (let file of workoutData.files) {
-            let a = document.createElement("a");
-            a.href = file.file;
-            let pathArray = file.file.split("/");
-            a.text = pathArray[pathArray.length - 1];
-            a.className = "me-2";
-
-            
-
-            let isImage = ["jpg", "png", "gif", "jpeg", "JPG", "PNG", "GIF", "JPEG"].includes(a.text.split(".")[1]);
-
-            if(isImage){
-                let deleteImgButton = document.createElement("input");
-                deleteImgButton.type = "button";
-                deleteImgButton.className = "btn btn-close";
-                deleteImgButton.id = file.url.split("/")[file.url.split("/").length - 2];
-                deleteImgButton.addEventListener('click', () => handleDeleteImgClick(deleteImgButton.id, "DELETE", `Could not delete workout ${deleteImgButton.id}!`, HOST, ["jpg", "png", "gif", "jpeg", "JPG", "PNG", "GIF", "JPEG"]));
-                filesDeleteDiv.appendChild(deleteImgButton);
-                
-                let img = document.createElement("img");
-                img.src = file.file;
-                
-                filesDiv.appendChild(img);
-                deleteImgButton.style.left = `${(fileCounter % 4) * 191}px`;
-                deleteImgButton.style.top = `${Math.floor(fileCounter / 4) * 105}px`;
-
-                if(isFirstImg){
-                    currentImageFileElement.src = file.file;
-                    isFirstImg = false;
-                }
-                fileCounter++;
+    noImageText.classList.add("hide");
+
+    let filesDiv = document.getElementById("img-collection");
+    let filesDeleteDiv = document.getElementById("img-collection-delete");
+
+    const currentImageFileElement = document.querySelector("#current");
+    let isFirstImg = true;
+
+    let fileCounter = 0;
+
+    for (let file of workoutData.files) {
+        let a = document.createElement("a");
+        a.href = file.file;
+        let pathArray = file.file.split("/");
+        a.text = pathArray[pathArray.length - 1];
+        a.className = "me-2";
+
+        let isImage = ["jpg", "png", "gif", "jpeg", "JPG", "PNG", "GIF", "JPEG"].includes(a.text.split(".")[1]);
+
+        if (isImage) {
+            let deleteImgButton = document.createElement("input");
+            deleteImgButton.type = "button";
+            deleteImgButton.className = "btn btn-close";
+            deleteImgButton.id = file.url.split("/")[file.url.split("/").length - 2];
+            deleteImgButton.addEventListener("click", () =>
+                handleDeleteImgClick(
+                    deleteImgButton.id,
+                    "DELETE",
+                    `Could not delete workout ${deleteImgButton.id}!`,
+                    HOST,
+                    ["jpg", "png", "gif", "jpeg", "JPG", "PNG", "GIF", "JPEG"]
+                )
+            );
+            filesDeleteDiv.appendChild(deleteImgButton);
+
+            let img = document.createElement("img");
+            img.src = file.file;
+
+            filesDiv.appendChild(img);
+            deleteImgButton.style.left = `${(fileCounter % 4) * 191}px`;
+            deleteImgButton.style.top = `${Math.floor(fileCounter / 4) * 105}px`;
+
+            if (isFirstImg) {
+                currentImageFileElement.src = file.file;
+                isFirstImg = false;
             }
+            fileCounter++;
         }
+    }
 
-        const otherImageFileElements = document.querySelectorAll(".imgs img");
-        const selectedOpacity = 0.6;
-        otherImageFileElements[0].style.opacity = selectedOpacity;
+    const otherImageFileElements = document.querySelectorAll(".imgs img");
+    const selectedOpacity = 0.6;
+    otherImageFileElements[0].style.opacity = selectedOpacity;
 
-        otherImageFileElements.forEach((imageFileElement) => imageFileElement.addEventListener("click", (event) => {
+    otherImageFileElements.forEach((imageFileElement) =>
+        imageFileElement.addEventListener("click", (event) => {
             //Changes the main image
             currentImageFileElement.src = event.target.src;
 
             //Adds the fade animation
-            currentImageFileElement.classList.add('fade-in')
-            setTimeout(() => currentImageFileElement.classList.remove('fade-in'), 500);
+            currentImageFileElement.classList.add("fade-in");
+            setTimeout(() => currentImageFileElement.classList.remove("fade-in"), 500);
 
             //Sets the opacity of the selected image to 0.4
-            otherImageFileElements.forEach((imageFileElement) => imageFileElement.style.opacity = 1)
+            otherImageFileElements.forEach((otherimageFileElement) => (otherimageFileElement.style.opacity = 1));
             event.target.style.opacity = selectedOpacity;
-        }))
-
-    }
-    return workoutData;     
+        })
+    );
+    return workoutData;
 }
 
 async function validateImgFileType(id, host_variable, acceptedFileTypes) {
     let file = await sendRequest("GET", `${host_variable}/api/workout-files/${id}/`);
     let fileData = await file.json();
     let fileType = fileData.file.split("/")[fileData.file.split("/").length - 1].split(".")[1];
-    
+
     return acceptedFileTypes.includes(fileType);
 }
 
-async function handleDeleteImgClick (id, http_keyword, fail_alert_text, host_variable, acceptedFileTypes) {
-    
-    if(validateImgFileType(id, host_variable, acceptedFileTypes, )){
-        return
+async function handleDeleteImgClick(id, http_keyword, fail_alert_text, host_variable, acceptedFileTypes) {
+    if (validateImgFileType(id, host_variable, acceptedFileTypes)) {
+        return;
     }
 
     let response = await sendRequest(http_keyword, `${host_variable}/api/workout-files/${id}/`);
@@ -115,17 +120,15 @@ async function handleDeleteImgClick (id, http_keyword, fail_alert_text, host_var
 
 function handleGoBackToWorkoutClick() {
     const urlParams = new URLSearchParams(window.location.search);
-    const id = urlParams.get('id');
+    const id = urlParams.get("id");
     window.location.replace(`workout.html?id=${id}`);
 }
 
 window.addEventListener("DOMContentLoaded", async () => {
-
     goBackButton = document.querySelector("#btn-back-workout");
-    goBackButton.addEventListener('click', handleGoBackToWorkoutClick);
+    goBackButton.addEventListener("click", handleGoBackToWorkoutClick);
 
     const urlParams = new URLSearchParams(window.location.search);
-    const id = urlParams.get('id');
-    let workoutData = await retrieveWorkoutImages(id);   
-
-});
\ No newline at end of file
+    const id = urlParams.get("id");
+    await retrieveWorkoutImages(id);
+});
diff --git a/frontend/www/scripts/group.js b/frontend/www/scripts/group.js
new file mode 100644
index 0000000000000000000000000000000000000000..51b5a1ebad408df1f3ebb9135caed7e21d4bb54b
--- /dev/null
+++ b/frontend/www/scripts/group.js
@@ -0,0 +1,47 @@
+async function generateGroupForm() {
+    let form = document.querySelector("#form-group");
+
+    let formData = new FormData(form);
+    let submitForm = new FormData();
+
+    let input = formData.get("group");
+    let groupName = input.split(":")[0];
+    submitForm.append("name", groupName);
+
+    let athletes = input.split(":")[1].replaceAll(" ", "").split(",");
+    let currentUser = await getCurrentUser();
+
+    for (let athlete of athletes) {
+        let response = await sendRequest(
+            "GET",
+            `${HOST}/api/users/${athlete}/`
+        );
+        let result = await response.json();
+        submitForm.append("athletes", result.url);
+    }
+
+    console.log(currentUser.url);
+    submitForm.append("coach", currentUser.url);
+    return submitForm;
+}
+
+async function createGroup() {
+    let submitForm = await generateGroupForm();
+
+    let response = await sendRequest(
+        "POST",
+        `${HOST}/api/groups/`,
+        submitForm,
+        ""
+    );
+
+    if (response.ok) {
+        document.getElementById("group-input").value = "";
+        window.location.reload();
+        return;
+    } else {
+        let data = await response.json();
+        let alert = createAlert("Could not create new workout!", data);
+        document.body.prepend(alert);
+    }
+}
diff --git a/frontend/www/scripts/groups.js b/frontend/www/scripts/groups.js
new file mode 100644
index 0000000000000000000000000000000000000000..0134c6c4448f3490d4d466587a12224e5b7a0d47
--- /dev/null
+++ b/frontend/www/scripts/groups.js
@@ -0,0 +1,12 @@
+async function getGroups() {
+    let groups = null;
+    let response = await sendRequest("GET", `${HOST}/api/groups/`);
+    if (!response.ok) {
+        console.log("COULD NOT RETRIEVE GROUPS");
+    } else {
+        let data = await response.json();
+        groups = data.results;
+    }
+
+    return groups;
+}
diff --git a/frontend/www/scripts/myathletes.js b/frontend/www/scripts/myathletes.js
index e02c573e3f1b0e7497a86b658a493bfd7ba45135..48f2a65b0ea57b1ccb64424366b3177d44ea53ce 100644
--- a/frontend/www/scripts/myathletes.js
+++ b/frontend/www/scripts/myathletes.js
@@ -1,6 +1,10 @@
 async function displayCurrentRoster() {
-    let templateFilledAthlete = document.querySelector("#template-filled-athlete");
-    let templateEmptyAthlete = document.querySelector("#template-empty-athlete");
+    let templateFilledAthlete = document.querySelector(
+        "#template-filled-athlete"
+    );
+    let templateEmptyAthlete = document.querySelector(
+        "#template-empty-athlete"
+    );
     let controls = document.querySelector("#controls");
 
     let currentUser = await getCurrentUser();
@@ -8,12 +12,20 @@ async function displayCurrentRoster() {
         let response = await sendRequest("GET", athleteUrl);
         let athlete = await response.json();
 
-        createFilledRow(templateFilledAthlete, athlete.username, controls, false);
+        createFilledRow(
+            templateFilledAthlete,
+            athlete.username,
+            controls,
+            false
+        );
     }
-    
-    let status = "p";   // pending
-    let category = "sent";  
-    let response = await sendRequest("GET", `${HOST}/api/offers/?status=${status}&category=${category}`);
+
+    let status = "p"; // pending
+    let category = "sent";
+    let response = await sendRequest(
+        "GET",
+        `${HOST}/api/offers/?status=${status}&category=${category}`
+    );
     if (!response.ok) {
         let data = await response.json();
         let alert = createAlert("Could not retrieve offers!", data);
@@ -24,7 +36,12 @@ async function displayCurrentRoster() {
         for (let offer of offers.results) {
             let response = await sendRequest("GET", offer.recipient);
             let recipient = await response.json();
-            createFilledRow(templateFilledAthlete, `${recipient.username} (pending)`, controls, true);
+            createFilledRow(
+                templateFilledAthlete,
+                `${recipient.username} (pending)`,
+                controls,
+                true
+            );
         }
     }
 
@@ -32,10 +49,16 @@ async function displayCurrentRoster() {
     let emptyDiv = emptyClone.querySelector("div");
     let emptyButton = emptyDiv.querySelector("button");
     emptyButton.addEventListener("click", addAthleteRow);
+
     controls.appendChild(emptyDiv);
 }
 
-function createFilledRow(templateFilledAthlete, inputValue, controls, disabled) {
+function createFilledRow(
+    templateFilledAthlete,
+    inputValue,
+    controls,
+    disabled
+) {
     let filledClone = templateFilledAthlete.content.cloneNode(true);
     let filledDiv = filledClone.querySelector("div");
     let filledInput = filledDiv.querySelector("input");
@@ -44,13 +67,27 @@ function createFilledRow(templateFilledAthlete, inputValue, controls, disabled)
     filledInput.disabled = disabled;
     if (!disabled) {
         filledButton.addEventListener("click", removeAthleteRow);
-    }
-    else {
+    } else {
         filledButton.disabled = true;
     }
     controls.appendChild(filledDiv);
 }
 
+function createFilledGroupRow(
+    templateFilledGroup,
+    inputValue,
+    controls,
+    disabled
+) {
+    let filledClone = templateFilledGroup.content.cloneNode(true);
+    let filledDiv = filledClone.querySelector("div");
+    let filledInput = filledDiv.querySelector("#card-body");
+    let filledButton = filledDiv.querySelector("button");
+    filledInput.innerHTML = inputValue;
+
+    controls.appendChild(filledDiv);
+}
+
 async function displayFiles() {
     let user = await getCurrentUser();
 
@@ -67,10 +104,18 @@ async function displayFiles() {
         response = await sendRequest("GET", file.athlete);
         let athlete = await response.json();
 
-        let tabPanel = document.querySelector(`#tab-contents-${athlete.username}`)
+        let tabPanel = document.querySelector(
+            `#tab-contents-${athlete.username}`
+        );
         if (!tabPanel) {
-            tabPanel = createTabContents(templateAthlete, athlete, listTab, templateFiles, navTabContent);
-        } 
+            tabPanel = createTabContents(
+                templateAthlete,
+                athlete,
+                listTab,
+                templateFiles,
+                navTabContent
+            );
+        }
 
         let divFiles = tabPanel.querySelector(".uploaded-files");
         let aFile = createFileLink(templateFile, file.file);
@@ -82,13 +127,26 @@ async function displayFiles() {
         let response = await sendRequest("GET", athleteUrl);
         let athlete = await response.json();
 
-        let tabPanel = document.querySelector(`#tab-contents-${athlete.username}`)
+        let tabPanel = document.querySelector(
+            `#tab-contents-${athlete.username}`
+        );
         if (!tabPanel) {
-            tabPanel = createTabContents(templateAthlete, athlete, listTab, templateFiles, navTabContent);
+            tabPanel = createTabContents(
+                templateAthlete,
+                athlete,
+                listTab,
+                templateFiles,
+                navTabContent
+            );
         }
-        let uploadBtn = document.querySelector(`#btn-upload-${athlete.username}`);
+        let uploadBtn = document.querySelector(
+            `#btn-upload-${athlete.username}`
+        );
         uploadBtn.disabled = false;
-        uploadBtn.addEventListener("click", async (event) => await uploadFiles(event, athlete));
+        uploadBtn.addEventListener(
+            "click",
+            async (event) => await uploadFiles(event, athlete)
+        );
 
         let fileInput = tabPanel.querySelector(".form-control");
         fileInput.disabled = false;
@@ -101,7 +159,44 @@ async function displayFiles() {
     }
 }
 
-function createTabContents(templateAthlete, athlete, listTab, templateFiles, navTabContent) {
+async function displayGroups() {
+    let templateFilledGroup = document.querySelector("#template-filled-group");
+    let groupControls = document.querySelector("#group-control");
+
+    // let currentUser = await getCurrentUser();
+    let groups = await getGroups();
+
+    // console.log(currentUser);
+    for (let group of groups) {
+        let members = "";
+        for (let athleteUrl of group.athletes) {
+            let response = await sendRequest("GET", athleteUrl);
+            let athlete = await response.json();
+            members += athlete.username + ", ";
+        }
+        if (members.length > 0) {
+            members = members.substring(0, members.length - 2);
+        }
+
+        // let response = await sendRequest("GET", groupUrl);
+        // let group = await response.json();
+
+        createFilledGroupRow(
+            templateFilledGroup,
+            "<strong>" + group.name + "</strong><br />" + members,
+            groupControls,
+            false
+        );
+    }
+}
+
+function createTabContents(
+    templateAthlete,
+    athlete,
+    listTab,
+    templateFiles,
+    navTabContent
+) {
     let cloneAthlete = templateAthlete.content.cloneNode(true);
 
     let a = cloneAthlete.querySelector("a");
@@ -144,37 +239,65 @@ function addAthleteRow(event) {
     event.currentTarget.addEventListener("click", removeAthleteRow);
 }
 
+function addGroupRow(event) {
+    let templateFilledGroup = document.querySelector("#template-filled-group");
+    let groupControls = document.querySelector("#group-control");
+
+    let filledGroupClone = templateFilledGroup.content.cloneNode(true);
+    let filledGroupDiv = filledGroupClone.querySelector("div");
+    let filledGroupButton = filledGroupDiv.querySelector("button");
+    filledGroupButton.addEventListener("click", removeGroupRow);
+
+    groupControls.appendChild(filledGroupDiv);
+}
+
 function removeAthleteRow(event) {
     event.currentTarget.parentElement.remove();
 }
+function removeGroupRow(event) {
+    event.currentTarget.parentElement.remove();
+}
 
 async function submitRoster() {
     let rosterInputs = document.querySelectorAll('input[name="athlete"]');
 
-    let body = {"athletes": []};
+    let body = { athletes: [] };
     let currentUser = await getCurrentUser();
 
     for (let rosterInput of rosterInputs) {
         if (!rosterInput.disabled && rosterInput.value) {
             // get user
-            let response = await sendRequest("GET", `${HOST}/api/users/${rosterInput.value}/`);
+            let response = await sendRequest(
+                "GET",
+                `${HOST}/api/users/${rosterInput.value}/`
+            );
             if (response.ok) {
                 let athlete = await response.json();
                 if (athlete.coach == currentUser.url) {
                     body.athletes.push(athlete.id);
                 } else {
                     // create offer
-                    let body = {'status': 'p', 'recipient': athlete.url};
-                    let response = await sendRequest("POST", `${HOST}/api/offers/`, body);
+                    let body = { status: "p", recipient: athlete.url };
+                    let response = await sendRequest(
+                        "POST",
+                        `${HOST}/api/offers/`,
+                        body
+                    );
                     if (!response.ok) {
                         let data = await response.json();
-                        let alert = createAlert("Could not create offer!", data);
+                        let alert = createAlert(
+                            "Could not create offer!",
+                            data
+                        );
                         document.body.prepend(alert);
                     }
                 }
             } else {
                 let data = await response.json();
-                let alert = createAlert(`Could not retrieve user ${rosterInput.value}!`, data);
+                let alert = createAlert(
+                    `Could not retrieve user ${rosterInput.value}!`,
+                    data
+                );
                 document.body.prepend(alert);
             }
         }
@@ -183,6 +306,62 @@ async function submitRoster() {
     location.reload();
 }
 
+async function submitGroup() {
+    // return;
+    let groupInput = document.querySelector("#group-input").value;
+
+    let users = groupInput.replaceAll(" ", "").split(",");
+    console.log(users);
+
+    // let body = { athletes: [] };
+    let currentUser = await getCurrentUser();
+
+    // Check if all users exist
+    for (let user of users) {
+        // get user
+        let response = await sendRequest("GET", `${HOST}/api/users/${user}/`);
+        if (!response.ok) {
+            console.log("User does not exist");
+            return;
+        }
+    }
+    // Create group
+
+    // Check if all users exist
+    for (let user of users) {
+        // get user
+        let response = await sendRequest("GET", `${HOST}/api/users/${user}/`);
+        if (response.ok) {
+            let athlete = await response.json();
+            if (athlete.coach == currentUser.url) {
+                body.athletes.push(athlete.id);
+            } else {
+                // create offer
+                let body = { status: "p", recipient: athlete.url };
+                let response = await sendRequest(
+                    "POST",
+                    `${HOST}/api/offers/`,
+                    body
+                );
+                if (!response.ok) {
+                    let data = await response.json();
+                    let alert = createAlert("Could not create offer!", data);
+                    document.body.prepend(alert);
+                }
+            }
+        } else {
+            let data = await response.json();
+            let alert = createAlert(
+                `Could not retrieve user ${rosterInput.value}!`,
+                data
+            );
+            document.body.prepend(alert);
+        }
+    }
+    let response = await sendRequest("PUT", currentUser.url, body);
+    location.reload();
+}
+
 async function uploadFiles(event, athlete) {
     let form = event.currentTarget.parentElement;
     let inputFormData = new FormData(form);
@@ -194,11 +373,18 @@ async function uploadFiles(event, athlete) {
             submitForm.append("file", file);
             submitForm.append("athlete", athlete.url);
 
-            let response = await sendRequest("POST", `${HOST}/api/athlete-files/`, submitForm, "");
+            let response = await sendRequest(
+                "POST",
+                `${HOST}/api/athlete-files/`,
+                submitForm,
+                ""
+            );
             if (response.ok) {
                 let data = await response.json();
 
-                let tabPanel = document.querySelector(`#tab-contents-${athlete.username}`)
+                let tabPanel = document.querySelector(
+                    `#tab-contents-${athlete.username}`
+                );
                 let divFiles = tabPanel.querySelector(".uploaded-files");
                 let aFile = createFileLink(templateFile, data["file"]);
                 divFiles.appendChild(aFile);
@@ -214,7 +400,23 @@ async function uploadFiles(event, athlete) {
 window.addEventListener("DOMContentLoaded", async () => {
     await displayCurrentRoster();
     await displayFiles();
-    
+    await displayGroups();
+
     let buttonSubmitRoster = document.querySelector("#button-submit-roster");
-    buttonSubmitRoster.addEventListener("click", async () => await submitRoster());
+    buttonSubmitRoster.addEventListener(
+        "click",
+        async () => await submitRoster()
+    );
+    let buttonSubmitGroup = document.querySelector("#button-submit-group");
+    let buttonSubmitChallenge = document.querySelector(
+        "#button-submit-challenge"
+    );
+    buttonSubmitGroup.addEventListener(
+        "click",
+        async () => await createGroup()
+    );
+    buttonSubmitChallenge.addEventListener(
+        "click",
+        async () => await createChallenge()
+    );
 });
diff --git a/frontend/www/scripts/mycoach.js b/frontend/www/scripts/mycoach.js
index 9441ba15efffbd0e6f6ca063d1b400b67f1bdf19..b12f78dffd4e44d625cc16cc4e11dee584f38c92 100644
--- a/frontend/www/scripts/mycoach.js
+++ b/frontend/www/scripts/mycoach.js
@@ -14,7 +14,7 @@ async function displayCurrentCoach() {
 
         input.value = coach.username;
     } else {
-        console.log("NO USER.COACH")
+        console.log("NO USER.COACH");
     }
 }
 
@@ -24,9 +24,12 @@ async function displayOffers() {
     let templateOffer = document.querySelector("#template-offer");
     let listOffers = document.querySelector("#list-offers");
 
-    let status = "p";   // pending
-    let category = "received";  
-    let response = await sendRequest("GET", `${HOST}/api/offers/?status=${status}&category=${category}`);
+    let status = "p"; // pending
+    let category = "received";
+    let response = await sendRequest(
+        "GET",
+        `${HOST}/api/offers/?status=${status}&category=${category}`
+    );
     if (!response.ok) {
         let data = await response.json();
         let alert = createAlert("Could not retrieve offers!", data);
@@ -38,16 +41,23 @@ async function displayOffers() {
             let li = cloneOffer.querySelector("li");
             let span = li.querySelector("span");
             span.textContent = `${offer.owner} wants to be your coach`;
-            
+
             let buttons = li.querySelectorAll("button");
             let acceptButton = buttons[0];
             let declineButton = buttons[1];
 
             //acceptButton.id = `btn-accept-${offer.id}`;
-            acceptButton.addEventListener("click", async (event) => await acceptOffer(event, offer.url, offer.owner));
+            acceptButton.addEventListener(
+                "click",
+                async (event) =>
+                    await acceptOffer(event, offer.url, offer.owner)
+            );
 
             //declineButton.id = `btn-decline-${offer.id}`;
-            declineButton.addEventListener("click", async (event) => await declineOffer(event, offer.url));
+            declineButton.addEventListener(
+                "click",
+                async (event) => await declineOffer(event, offer.url)
+            );
 
             listOffers.appendChild(li);
         }
@@ -62,7 +72,7 @@ async function displayOffers() {
 
 async function acceptOffer(event, offerUrl, ownerUsername) {
     let button = event.currentTarget;
-    let body = {"status": "d"};
+    let body = { status: "d" };
 
     let response = await sendRequest("PATCH", offerUrl, body);
     if (!response.ok) {
@@ -70,11 +80,14 @@ async function acceptOffer(event, offerUrl, ownerUsername) {
         let alert = createAlert("Could not accept offer!", data);
         document.body.prepend(alert);
     } else {
-        let response = await sendRequest("GET", `${HOST}/api/users/${ownerUsername}/`);
+        let response = await sendRequest(
+            "GET",
+            `${HOST}/api/users/${ownerUsername}/`
+        );
         let owner = await response.json();
         let user = await getCurrentUser();
 
-        let body = {'coach': owner.url};
+        let body = { coach: owner.url };
         response = await sendRequest("PATCH", user.url, body);
 
         if (!response.ok) {
@@ -86,12 +99,11 @@ async function acceptOffer(event, offerUrl, ownerUsername) {
             return false;
         }
     }
-
 }
 
 async function declineOffer(event, offerUrl) {
     let button = event.currentTarget;
-    let body = {'status': 'd'};
+    let body = { status: "d" };
 
     let response = await sendRequest("PATCH", offerUrl, body);
     if (!response.ok) {
@@ -109,7 +121,7 @@ async function displayFiles() {
 
     let templateOwner = document.querySelector("#template-owner-tab");
     let templateFiles = document.querySelector("#template-files");
-    let templateFile = document.querySelector("#template-file")
+    let templateFile = document.querySelector("#template-file");
     let listTab = document.querySelector("#list-tab");
     let navTabContent = document.querySelector("#nav-tabContent");
 
@@ -155,7 +167,7 @@ async function displayFiles() {
 }
 
 async function getReceivedRequests() {
-    let response = await sendRequest("GET", `${HOST}/api/athlete-requests/`)
+    let response = await sendRequest("GET", `${HOST}/api/athlete-requests/`);
     if (!response.ok) {
         let data = await response.json();
         let alert = createAlert("Could not retrieve athlete request!", data);
@@ -164,13 +176,19 @@ async function getReceivedRequests() {
         let data = await response.json();
         let athleteRequests = data.results;
         for (let athleteRequest of athleteRequests) {
-            if (athleteRequest.recipient == sessionStorage.getItem("username")) {
-                let div = document.querySelector("#div-received-athlete-requests");
-                let template = document.querySelector("#template-athlete-request");
+            if (
+                athleteRequest.recipient == sessionStorage.getItem("username")
+            ) {
+                let div = document.querySelector(
+                    "#div-received-athlete-requests"
+                );
+                let template = document.querySelector(
+                    "#template-athlete-request"
+                );
 
                 let clone = template.content.firstElementChild.cloneNode(true);
                 let button = clone.querySelector("button");
-                button.textContent = `${athleteRequest.owner} wants to be your coach!`
+                button.textContent = `${athleteRequest.owner} wants to be your coach!`;
 
                 div.appendChild(clone);
             }
@@ -187,7 +205,10 @@ function editCoach(event) {
 
     buttonEditCoach.className += " hide";
     buttonSetCoach.className = buttonSetCoach.className.replace(" hide", "");
-    buttonCancelCoach.className = buttonCancelCoach.className.replace(" hide", "");
+    buttonCancelCoach.className = buttonCancelCoach.className.replace(
+        " hide",
+        ""
+    );
 }
 
 function cancelCoach() {
@@ -201,19 +222,25 @@ async function setCoach() {
     let newCoach = document.querySelector("#input-coach").value;
     let body = {};
     if (!newCoach) {
-        body['coach'] = null;
+        body["coach"] = null;
     } else {
-        let response = await sendRequest("GET", `${HOST}/api/users/${newCoach}/`)
+        let response = await sendRequest(
+            "GET",
+            `${HOST}/api/users/${newCoach}/`
+        );
         if (!response.ok) {
             let data = await response.json();
-            let alert = createAlert(`Could not retrieve user ${newCoach}`, data);
+            let alert = createAlert(
+                `Could not retrieve user ${newCoach}`,
+                data
+            );
             document.body.prepend(alert);
         }
         let newCoachObject = await response.json();
-        body['coach'] = newCoachObject.url;
+        body["coach"] = newCoachObject.url;
     }
 
-    if ('coach' in body) {
+    if ("coach" in body) {
         let response = await sendRequest("PATCH", user.url, body);
         if (!response.ok) {
             let data = await response.json();
@@ -234,8 +261,11 @@ window.addEventListener("DOMContentLoaded", async () => {
     let buttonSetCoach = document.querySelector("#button-set-coach");
     let buttonEditCoach = document.querySelector("#button-edit-coach");
     let buttonCancelCoach = document.querySelector("#button-cancel-coach");
-    
-    buttonSetCoach.addEventListener("click", async (event) => await setCoach(event));
+
+    buttonSetCoach.addEventListener(
+        "click",
+        async (event) => await setCoach(event)
+    );
     buttonEditCoach.addEventListener("click", editCoach);
     buttonCancelCoach.addEventListener("click", cancelCoach);
-});
\ No newline at end of file
+});
diff --git a/frontend/www/scripts/navbar.js b/frontend/www/scripts/navbar.js
index 8dec8c1a20089ea9bbb85e5df99001473ec64c2c..2e87897562c4c1200e96a25e49d09a97d432c76f 100644
--- a/frontend/www/scripts/navbar.js
+++ b/frontend/www/scripts/navbar.js
@@ -24,15 +24,14 @@ class NavBar extends HTMLElement {
             <div class="my-2 my-lg-0 me-5">
                 <a role="button" class="btn btn-primary hide" id="btn-register" href="register.html">Register</a>
                 <a role="button" class="btn btn-secondary hide" id="btn-login-nav" href="login.html">Log in</a>
+                <a role="button" class="btn btn-primary hide" id="btn-logout" href="profile.html">Profile</a>
                 <a role="button" class="btn btn-secondary hide" id="btn-logout" href="logout.html">Log out</a>
             </div>
             </div>
         </div>
         </nav>
         `;
-
-        
     }
 }
 
-customElements.define('navbar-el', NavBar);
\ No newline at end of file
+customElements.define("navbar-el", NavBar);
diff --git a/frontend/www/scripts/profile.js b/frontend/www/scripts/profile.js
new file mode 100644
index 0000000000000000000000000000000000000000..e34c94de41542d4c4e16d982ac4555c8e2d994f9
--- /dev/null
+++ b/frontend/www/scripts/profile.js
@@ -0,0 +1,40 @@
+async function displayUser() {
+    let user = await getCurrentUser();
+
+    if (user) {
+        let form = document.querySelector("#form-user");
+        let formData = new FormData(form);
+
+        for (let key of formData.keys()) {
+            let selector;
+            key !== "muscleGroup"
+                ? (selector = `input[name="${key}"], textarea[name="${key}"]`)
+                : (selector = `select[name=${key}]`);
+            let input = form.querySelector(selector);
+            let newVal = user[key];
+            if (!newVal) newVal = "N/A";
+            input.value = newVal;
+        }
+        document.querySelector("select").setAttribute("disabled", "");
+    } else {
+        let alert = createAlert("Could not find user", {
+            detail: "No user is found",
+        });
+        document.body.prepend(alert);
+    }
+}
+
+window.addEventListener("DOMContentLoaded", async () => {
+    await displayUser();
+
+    // let buttonSetCoach = document.querySelector("#button-set-coach");
+    // let buttonEditCoach = document.querySelector("#button-edit-coach");
+    // let buttonCancelCoach = document.querySelector("#button-cancel-coach");
+
+    // buttonSetCoach.addEventListener(
+    //     "click",
+    //     async (event) => await setCoach(event)
+    // );
+    // buttonEditCoach.addEventListener("click", editCoach);
+    // buttonCancelCoach.addEventListener("click", cancelCoach);
+});
diff --git a/frontend/www/scripts/scripts.js b/frontend/www/scripts/scripts.js
index 9bfc1efb688400bcabfa9457b05be97f8dbd9c92..a87eb48083f0787acb2e3293efec796be908f02d 100644
--- a/frontend/www/scripts/scripts.js
+++ b/frontend/www/scripts/scripts.js
@@ -1,197 +1,217 @@
 function makeNavLinkActive(id) {
-  let link = document.getElementById(id);
-  link.classList.add("active");
-  link.setAttribute("aria-current", "page");
+    let link = document.getElementById(id);
+    link.classList.add("active");
+    link.setAttribute("aria-current", "page");
 }
 
 function isUserAuthenticated() {
-  return (getCookieValue("access") != null) || (getCookieValue("refresh") != null);
+    return (
+        getCookieValue("access") != null || getCookieValue("refresh") != null
+    );
 }
 
 function updateNavBar() {
-  let nav = document.querySelector("nav");
-
-  // Emphasize link to current page
-  if (window.location.pathname == "/" || window.location.pathname == "/index.html") {
-    makeNavLinkActive("nav-index");
-  } else if (window.location.pathname == "/workouts.html") {
-    makeNavLinkActive("nav-workouts");
-  } else if (window.location.pathname == "/exercises.html") {
-    makeNavLinkActive("nav-exercises");
-  } else if (window.location.pathname == "/mycoach.html") {
-    makeNavLinkActive("nav-mycoach")
-  } else if (window.location.pathname == "/myathletes.html") {
-    makeNavLinkActive("nav-myathletes");
-  } else if (window.location.pathname == "/meals.html") {
-    makeNavLinkActive("nav-myathletes");
-  }
-
-  if (isUserAuthenticated()) {
-    document.getElementById("btn-logout").classList.remove("hide");
-
-    document.querySelector('a[href="logout.html"').classList.remove("hide");
-    document.querySelector('a[href="workouts.html"').classList.remove("hide");
-    document.querySelector('a[href="mycoach.html"').classList.remove("hide");
-    document.querySelector('a[href="exercises.html"').classList.remove("hide");
-    document.querySelector('a[href="myathletes.html"').classList.remove("hide");
-    document.querySelector('a[href="meals.html"').classList.remove("hide");
-  } else {
-    document.getElementById("btn-login-nav").classList.remove("hide");
-    document.getElementById("btn-register").classList.remove("hide");
-  }
+    let nav = document.querySelector("nav");
+
+    // Emphasize link to current page
+    if (
+        window.location.pathname == "/" ||
+        window.location.pathname == "/index.html"
+    ) {
+        makeNavLinkActive("nav-index");
+    } else if (window.location.pathname == "/workouts.html") {
+        makeNavLinkActive("nav-workouts");
+    } else if (window.location.pathname == "/exercises.html") {
+        makeNavLinkActive("nav-exercises");
+    } else if (window.location.pathname == "/mycoach.html") {
+        makeNavLinkActive("nav-mycoach");
+    } else if (window.location.pathname == "/myathletes.html") {
+        makeNavLinkActive("nav-myathletes");
+    } else if (window.location.pathname == "/meals.html") {
+        makeNavLinkActive("nav-myathletes");
+    }
 
+    if (isUserAuthenticated()) {
+        document.getElementById("btn-logout").classList.remove("hide");
+
+        document.querySelector('a[href="logout.html"').classList.remove("hide");
+        document
+            .querySelector('a[href="workouts.html"')
+            .classList.remove("hide");
+        document
+            .querySelector('a[href="mycoach.html"')
+            .classList.remove("hide");
+        document
+            .querySelector('a[href="exercises.html"')
+            .classList.remove("hide");
+        document
+            .querySelector('a[href="myathletes.html"')
+            .classList.remove("hide");
+        document.querySelector('a[href="meals.html"').classList.remove("hide");
+    } else {
+        document.getElementById("btn-login-nav").classList.remove("hide");
+        document.getElementById("btn-register").classList.remove("hide");
+    }
 }
 
-
-function setCookie(name, value, maxage, path="") {
-  document.cookie = `${name}=${value}; max-age=${maxage}; path=${path}`;
+function setCookie(name, value, maxage, path = "") {
+    document.cookie = `${name}=${value}; max-age=${maxage}; path=${path}`;
 }
 
 function deleteCookie(name) {
-  setCookie(name, "", 0, "/");
+    setCookie(name, "", 0, "/");
 }
 
 function getCookieValue(name) {
-  let cookieValue = null;
-  let cookieByName = document.cookie.split("; ").find(row => row.startsWith(name));
+    let cookieValue = null;
+    let cookieByName = document.cookie
+        .split("; ")
+        .find((row) => row.startsWith(name));
 
-  if (cookieByName) {
-    cookieValue = cookieByName.split("=")[1];
-  }
+    if (cookieByName) {
+        cookieValue = cookieByName.split("=")[1];
+    }
 
-  return cookieValue;
+    return cookieValue;
 }
 
-async function sendRequest(method, url, body, contentType="application/json; charset=UTF-8") {
-  if (body && contentType.includes("json")) {
-    body = JSON.stringify(body);
-  }
-
-  let myHeaders = new Headers();
-
-  if (contentType) myHeaders.set("Content-Type", contentType);
-  if (getCookieValue("access")) myHeaders.set("Authorization", "Bearer " + getCookieValue("access"));
-  let myInit = {headers: myHeaders, method: method, body: body};
-  let myRequest = new Request(url, myInit);
-
-  let response = await fetch(myRequest);
-  if (response.status == 401 && getCookieValue("refresh")) {
-    // access token not accepted. getting refresh token
-    myHeaders = new Headers({"Content-Type": "application/json; charset=UTF-8"});
-    let tokenBody = JSON.stringify({"refresh": getCookieValue("refresh")});
-    myInit = {headers: myHeaders, method: "POST", body: tokenBody};
-    myRequest = new Request(`${HOST}/api/token/refresh/`, myInit);
-    response = await fetch(myRequest);
-
-    if (response.ok) {
-      // refresh successful, received new access token
-      let data = await response.json();
-      setCookie("access", data.access, 86400, "/");
-
-        let myHeaders = new Headers({"Authorization": "Bearer " + getCookieValue("access"),
-                               "Content-Type": contentType});
-        let myInit = {headers: myHeaders, method: method, body: body};
-        let myRequest = new Request(url, myInit);
+async function sendRequest(
+    method,
+    url,
+    body,
+    contentType = "application/json; charset=UTF-8"
+) {
+    if (body && contentType.includes("json")) {
+        body = JSON.stringify(body);
+    }
+
+    let myHeaders = new Headers();
+
+    if (contentType) myHeaders.set("Content-Type", contentType);
+    if (getCookieValue("access"))
+        myHeaders.set("Authorization", "Bearer " + getCookieValue("access"));
+    let myInit = { headers: myHeaders, method: method, body: body };
+    let myRequest = new Request(url, myInit);
+
+    let response = await fetch(myRequest);
+    if (response.status == 401 && getCookieValue("refresh")) {
+        // access token not accepted. getting refresh token
+        myHeaders = new Headers({
+            "Content-Type": "application/json; charset=UTF-8",
+        });
+        let tokenBody = JSON.stringify({ refresh: getCookieValue("refresh") });
+        myInit = { headers: myHeaders, method: "POST", body: tokenBody };
+        myRequest = new Request(`${HOST}/api/token/refresh/`, myInit);
         response = await fetch(myRequest);
 
-        if (!response.ok) window.location.replace("logout.html");
+        if (response.ok) {
+            // refresh successful, received new access token
+            let data = await response.json();
+            setCookie("access", data.access, 86400, "/");
+
+            let myHeaders = new Headers({
+                Authorization: "Bearer " + getCookieValue("access"),
+                "Content-Type": contentType,
+            });
+            let myInit = { headers: myHeaders, method: method, body: body };
+            let myRequest = new Request(url, myInit);
+            response = await fetch(myRequest);
+
+            if (!response.ok) window.location.replace("logout.html");
+        }
     }
-  }
 
-  return response;
+    return response;
 }
 
 function setReadOnly(readOnly, selector) {
-  let form = document.querySelector(selector);
-  let formData = new FormData(form);
-
-  for (let key of formData.keys()) {
-      let selector = `input[name="${key}"], textarea[name="${key}"]`;
-      for (let input of form.querySelectorAll(selector)) {
-
-      if (!readOnly && input.hasAttribute("readonly"))
-      {
-        input.readOnly = false;
-      }
-      else if (readOnly && !input.hasAttribute("readonly")) {
-        input.readOnly = true;
-      }
+    let form = document.querySelector(selector);
+    let formData = new FormData(form);
+
+    for (let key of formData.keys()) {
+        let selector = `input[name="${key}"], textarea[name="${key}"]`;
+        for (let input of form.querySelectorAll(selector)) {
+            if (!readOnly && input.hasAttribute("readonly")) {
+                input.readOnly = false;
+            } else if (readOnly && !input.hasAttribute("readonly")) {
+                input.readOnly = true;
+            }
+        }
+
+        selector = `input[type="file"], select[name="${key}`;
+        for (let input of form.querySelectorAll(selector)) {
+            if (!readOnly && input.hasAttribute("disabled")) {
+                input.disabled = false;
+            } else if (readOnly && !input.hasAttribute("disabled")) {
+                input.disabled = true;
+            }
+        }
     }
 
-    selector = `input[type="file"], select[name="${key}`;
-    for (let input of form.querySelectorAll(selector)) {
-      if ((!readOnly && input.hasAttribute("disabled")))
-      {
-        input.disabled = false;
-      }
-      else if (readOnly && !input.hasAttribute("disabled")) {
-        input.disabled = true;
-      } 
+    for (let input of document.querySelectorAll(
+        "input:disabled, select:disabled"
+    )) {
+        if (
+            (!readOnly && input.hasAttribute("disabled")) ||
+            (readOnly && !input.hasAttribute("disabled"))
+        ) {
+            input.disabled = !input.disabled;
+        }
     }
-  }
-
-  for (let input of document.querySelectorAll("input:disabled, select:disabled")) {
-    if ((!readOnly && input.hasAttribute("disabled")) ||
-        (readOnly && !input.hasAttribute("disabled"))) {
-        input.disabled = !input.disabled;
-    } 
-  }
 }
 
 async function getCurrentUser() {
-  let user = null;
-  let response = await sendRequest("GET", `${HOST}/api/users/?user=current`)
-  if (!response.ok) {
-      console.log("COULD NOT RETRIEVE CURRENTLY LOGGED IN USER");
-  } else {
-      let data = await response.json();
-      user = data.results[0];
-  }
-
-  return user;
+    let user = null;
+    let response = await sendRequest("GET", `${HOST}/api/users/?user=current`);
+    if (!response.ok) {
+        console.log("COULD NOT RETRIEVE CURRENTLY LOGGED IN USER");
+    } else {
+        let data = await response.json();
+        user = data.results[0];
+    }
+
+    return user;
 }
 
 function createAlert(header, data) {
-  let alertDiv = document.createElement("div");
-  alertDiv.className = "alert alert-warning alert-dismissible fade show"
-  alertDiv.setAttribute("role", "alert");
-  
-  let strong = document.createElement("strong");
-  strong.innerText = header;
-  alertDiv.appendChild(strong);
-
-  let button = document.createElement("button");
-  button.type = "button";
-  button.className = "btn-close";
-  button.setAttribute("data-bs-dismiss", "alert");
-  button.setAttribute("aria-label", "Close");
-  alertDiv.appendChild(button);
-
-  let ul = document.createElement("ul");
-  if ("detail" in data) {
-    let li = document.createElement("li");
-    li.innerText = data["detail"];
-    ul.appendChild(li);
-  } else {
-    for (let key in data) {
-      let li = document.createElement("li");
-      li.innerText = key;
-
-      let innerUl = document.createElement("ul");
-      for (let message of data[key]) {
-        let innerLi = document.createElement("li");
-        innerLi.innerText = message;
-        innerUl.appendChild(innerLi);
-      }
-      li.appendChild(innerUl);
-      ul.appendChild(li);
+    let alertDiv = document.createElement("div");
+    alertDiv.className = "alert alert-warning alert-dismissible fade show";
+    alertDiv.setAttribute("role", "alert");
+
+    let strong = document.createElement("strong");
+    strong.innerText = header;
+    alertDiv.appendChild(strong);
+
+    let button = document.createElement("button");
+    button.type = "button";
+    button.className = "btn-close";
+    button.setAttribute("data-bs-dismiss", "alert");
+    button.setAttribute("aria-label", "Close");
+    alertDiv.appendChild(button);
+
+    let ul = document.createElement("ul");
+    if ("detail" in data) {
+        let li = document.createElement("li");
+        li.innerText = data["detail"];
+        ul.appendChild(li);
+    } else {
+        for (let key in data) {
+            let li = document.createElement("li");
+            li.innerText = key;
+
+            let innerUl = document.createElement("ul");
+            for (let message of data[key]) {
+                let innerLi = document.createElement("li");
+                innerLi.innerText = message;
+                innerUl.appendChild(innerLi);
+            }
+            li.appendChild(innerUl);
+            ul.appendChild(li);
+        }
     }
-  }
-  alertDiv.appendChild(ul);
-
-  return alertDiv;
+    alertDiv.appendChild(ul);
 
+    return alertDiv;
 }
 
 window.addEventListener("DOMContentLoaded", updateNavBar);
-
diff --git a/requirements.txt b/requirements.txt
index 9feb375bde1e8fb7befe6c102dd29beeee7c6940..90ad84fd05c418f46c879cbf5397a00bd210408b 100644
Binary files a/requirements.txt and b/requirements.txt differ