Repository: Learnathon-By-Geeky-Solutions/seagull
Pull request #56: Pytest
Author: login: ImamIfti056
Created: 2025-04-12T16:52:26Z | Merged: 2025-04-12T17:26:14Z



Diff:
diff --git a/server/.gitignore b/server/.gitignore
index cf669750..7e28d12f 100644
--- a/server/.gitignore
+++ b/server/.gitignore
@@ -10,8 +10,8 @@ __pycache__/
 backend/db.sqlite3
 
 # Ignore Django migrations
-**/migrations/*
-!**/migrations/__init__.py
+# **/migrations/*
+# !**/migrations/__init__.py
 
 # Environment Variables
 .env
diff --git a/server/backend/backend/settings.py b/server/backend/backend/settings.py
index aa0df7fd..315cd113 100644
--- a/server/backend/backend/settings.py
+++ b/server/backend/backend/settings.py
@@ -107,8 +107,6 @@
             'PASSWORD': os.getenv('POSTGRES_PASSWORD', 'postgres'),
             'HOST': 'localhost',
             'PORT': '5432',
-        }
-    }
 else:
     DATABASES = {
         'default': {
@@ -118,8 +116,6 @@
             'PASSWORD': config('DB_PASSWORD'),
             'HOST': config('DB_HOST'),
             'PORT': config('DB_PORT', cast=int),
-        }
-    }
 
 
 # Password validation
@@ -180,4 +176,4 @@
     "ROTATE_REFRESH_TOKENS": False,
     "BLACKLIST_AFTER_ROTATION": True,
     "AUTH_HEADER_TYPES": ("Bearer",),
-}
+}
\ No newline at end of file
diff --git a/server/backend/courses/migrations/0001_initial.py b/server/backend/courses/migrations/0001_initial.py
new file mode 100644
index 00000000..1c222175
--- /dev/null
+++ b/server/backend/courses/migrations/0001_initial.py
@@ -0,0 +1,157 @@
+# Generated by Django 5.1.4 on 2025-03-19 21:21
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    initial = True
+
+    dependencies = [
+        ("users", "__first__"),
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="Course",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("title", models.CharField(max_length=255)),
+                ("description", models.TextField()),
+                ("duration", models.PositiveIntegerField()),
+                (
+                    "difficulty",
+                    models.CharField(
+                        choices=[
+                            ("beginner", "Beginner"),
+                            ("intermediate", "Intermediate"),
+                            ("advanced", "Advanced"),
+                        ],
+                        max_length=20,
+                    ),
+                ),
+                ("subject", models.CharField(max_length=255)),
+                ("ratings", models.FloatField(default=0.0)),
+                (
+                    "created_by",
+                    models.ForeignKey(
+                        null=True,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to="users.instructor",
+                    ),
+                ),
+            ],
+        ),
+        migrations.CreateModel(
+            name="CourseContents",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "content_type",
+                    models.CharField(
+                        choices=[
+                            ("video", "Video"),
+                            ("pdf", "PDF"),
+                            ("text", "Text"),
+                            ("article", "Article"),
+                        ],
+                        max_length=20,
+                    ),
+                ),
+                ("title", models.CharField(max_length=255)),
+                ("url", models.URLField(blank=True, null=True)),
+                ("text_content", models.TextField(blank=True)),
+                (
+                    "course",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="contents",
+                        to="courses.course",
+                    ),
+                ),
+            ],
+        ),
+        migrations.CreateModel(
+            name="Enrollment",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("enrolled_at", models.DateTimeField(auto_now_add=True)),
+                (
+                    "course",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE, to="courses.course"
+                    ),
+                ),
+                (
+                    "student",
+                    models.ForeignKey(
+                        null=True,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to="users.student",
+                    ),
+                ),
+            ],
+            options={
+                "unique_together": {("course", "student")},
+            },
+        ),
+        migrations.CreateModel(
+            name="Rating",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("rating", models.PositiveSmallIntegerField()),
+                (
+                    "course",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="ratings_set",
+                        to="courses.course",
+                    ),
+                ),
+                (
+                    "user",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to=settings.AUTH_USER_MODEL,
+                    ),
+                ),
+            ],
+            options={
+                "unique_together": {("course", "user")},
+            },
+        ),
+    ]
diff --git a/server/backend/courses/migrations/0002_course_image_alter_rating_rating.py b/server/backend/courses/migrations/0002_course_image_alter_rating_rating.py
new file mode 100644
index 00000000..f2e3e75b
--- /dev/null
+++ b/server/backend/courses/migrations/0002_course_image_alter_rating_rating.py
@@ -0,0 +1,28 @@
+# Generated by Django 5.1.8 on 2025-04-08 08:46
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("courses", "0001_initial"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="course",
+            name="image",
+            field=models.URLField(blank=True, null=True),
+        ),
+        migrations.AlterField(
+            model_name="rating",
+            name="rating",
+            field=models.PositiveSmallIntegerField(
+                validators=[
+                    django.core.validators.MinValueValidator(1),
+                    django.core.validators.MaxValueValidator(5),
+                ]
+            ),
+        ),
+    ]
diff --git a/server/backend/coverage.xml b/server/backend/coverage.xml
index a3d0323f..286aee4a 100644
--- a/server/backend/coverage.xml
+++ b/server/backend/coverage.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" ?>
-<coverage version="7.8.0" timestamp="1744376712667" lines-valid="38" lines-covered="30" line-rate="0.7895" branches-covered="0" branches-valid="0" branch-rate="0" complexity="0">
+<coverage version="7.8.0" timestamp="1744471816957" lines-valid="38" lines-covered="30" line-rate="0.7895" branches-covered="0" branches-valid="0" branch-rate="0" complexity="0">
 	<!-- Generated by coverage.py: https://coverage.readthedocs.io/en/7.8.0 -->
 	<!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
 	<sources>
diff --git a/server/backend/forum/migrations/0001_initial.py b/server/backend/forum/migrations/0001_initial.py
new file mode 100644
index 00000000..0b7e5121
--- /dev/null
+++ b/server/backend/forum/migrations/0001_initial.py
@@ -0,0 +1,143 @@
+# Generated by Django 5.1.4 on 2025-04-05 17:47
+
+import django.db.models.deletion
+import mptt.fields
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="Tag",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("name", models.CharField(max_length=50, unique=True)),
+            ],
+        ),
+        migrations.CreateModel(
+            name="Post",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("title", models.CharField(max_length=200)),
+                ("content", models.TextField()),
+                ("created_at", models.DateTimeField(auto_now_add=True)),
+                ("updated_at", models.DateTimeField(auto_now=True)),
+                (
+                    "author",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to=settings.AUTH_USER_MODEL,
+                    ),
+                ),
+                ("tags", models.ManyToManyField(related_name="posts", to="forum.tag")),
+            ],
+        ),
+        migrations.CreateModel(
+            name="Comment",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("content", models.TextField()),
+                ("created_at", models.DateTimeField(auto_now_add=True)),
+                ("updated_at", models.DateTimeField(auto_now=True)),
+                ("lft", models.PositiveIntegerField(editable=False)),
+                ("rght", models.PositiveIntegerField(editable=False)),
+                ("tree_id", models.PositiveIntegerField(db_index=True, editable=False)),
+                ("level", models.PositiveIntegerField(editable=False)),
+                (
+                    "parent",
+                    mptt.fields.TreeForeignKey(
+                        blank=True,
+                        null=True,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="children",
+                        to="forum.comment",
+                    ),
+                ),
+                (
+                    "user",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to=settings.AUTH_USER_MODEL,
+                    ),
+                ),
+                (
+                    "post",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="comments",
+                        to="forum.post",
+                    ),
+                ),
+            ],
+            options={
+                "abstract": False,
+            },
+        ),
+        migrations.CreateModel(
+            name="Vote",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "value",
+                    models.SmallIntegerField(choices=[(1, "Upvote"), (-1, "Downvote")]),
+                ),
+                (
+                    "post",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="votes",
+                        to="forum.post",
+                    ),
+                ),
+                (
+                    "user",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to=settings.AUTH_USER_MODEL,
+                    ),
+                ),
+            ],
+            options={
+                "unique_together": {("user", "post")},
+            },
+        ),
+    ]
diff --git a/server/backend/forum/serializers.py b/server/backend/forum/serializers.py
index ef5813c2..a8c1df95 100644
--- a/server/backend/forum/serializers.py
+++ b/server/backend/forum/serializers.py
@@ -1,6 +1,5 @@
 from rest_framework import serializers
 from .models import Post, Comment, Vote,Tag
-from mptt.templatetags.mptt_tags import cache_tree_children
 
 
 class TagSerializer(serializers.ModelSerializer):
diff --git a/server/backend/forum/tests/__init__.py b/server/backend/forum/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/server/backend/forum/tests/test_models.py b/server/backend/forum/tests/test_models.py
new file mode 100644
index 00000000..8205aa53
--- /dev/null
+++ b/server/backend/forum/tests/test_models.py
@@ -0,0 +1,62 @@
+import pytest
+from forum.models import Tag, Post, Comment, Vote
+from users.models import User
+
+@pytest.mark.django_db
+class TestModels:
+
+    def test_tag_creation(self):
+        tag = Tag.objects.create(name="Django")
+        assert tag.name == "Django"
+        assert str(tag) == "Django"
+
+    def test_post_creation(self):
+        user = User.objects.create_user(username="testuser", password="password")
+        tag = Tag.objects.create(name="Python")
+        post = Post.objects.create(author=user, title="Test Post", content="Test Content")
+        post.tags.add(tag)
+        
+        assert post.author == user
+        assert post.title == "Test Post"
+        assert post.content == "Test Content"
+        assert post.tags.count() == 1
+        assert str(post) == "Test Post"
+
+    def test_comment_creation(self):
+        user = User.objects.create_user(username="commenter", password="password")
+        post = Post.objects.create(author=user, title="Another Post", content="More Content")
+        comment = Comment.objects.create(user=user, post=post, content="Nice post!")
+
+        assert comment.user == user
+        assert comment.post == post
+        assert comment.content == "Nice post!"
+        assert str(comment) == f"Comment by {user} on {post}"
+
+    def test_comment_reply(self):
+        user = User.objects.create_user(username="replier", password="password")
+        post = Post.objects.create(author=user, title="Main Post", content="Main Content")
+        parent_comment = Comment.objects.create(user=user, post=post, content="Parent comment")
+        reply_comment = Comment.objects.create(user=user, post=post, content="Reply comment", parent=parent_comment)
+
+        assert reply_comment.parent == parent_comment
+        assert reply_comment.post == post
+
+    def test_vote_creation(self):
+        user = User.objects.create_user(username="voter", password="password")
+        post = Post.objects.create(author=user, title="Vote Post", content="Vote Content")
+        vote = Vote.objects.create(user=user, post=post, value=1)
+
+        assert vote.user == user
+        assert vote.post == post
+        assert vote.value == 1
+
+    def test_total_votes(self):
+        user1 = User.objects.create_user(username="user1", password="password")
+        user2 = User.objects.create_user(username="user2", password="password")
+        post = Post.objects.create(author=user1, title="Voting Post", content="Vote please!")
+
+        Vote.objects.create(user=user1, post=post, value=1)
+        Vote.objects.create(user=user2, post=post, value=-1)
+
+        assert post.total_votes() == 0  # 1 - 1 = 0
+
diff --git a/server/backend/forum/tests/test_serializers.py b/server/backend/forum/tests/test_serializers.py
new file mode 100644
index 00000000..e69de29b
diff --git a/server/backend/forum/tests/test_views.py b/server/backend/forum/tests/test_views.py
new file mode 100644
index 00000000..e69de29b
diff --git a/server/backend/forum/views.py b/server/backend/forum/views.py
index 278cbae3..588a6242 100644
--- a/server/backend/forum/views.py
+++ b/server/backend/forum/views.py
@@ -9,8 +9,6 @@
 from django.db.models import Count, Sum, Case, When, IntegerField, F,Q
 
 
-
-
 class PostViewSet(viewsets.ModelViewSet):
     queryset = Post.objects.all().order_by('-created_at').prefetch_related('tags')
     serializer_class = PostSerializer
diff --git a/server/backend/quiz/migrations/0001_initial.py b/server/backend/quiz/migrations/0001_initial.py
new file mode 100644
index 00000000..e5eb8772
--- /dev/null
+++ b/server/backend/quiz/migrations/0001_initial.py
@@ -0,0 +1,148 @@
+# Generated by Django 5.1.4 on 2025-04-05 17:47
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="Category",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("name", models.CharField(max_length=100)),
+                ("description", models.TextField()),
+                ("created_at", models.DateTimeField(auto_now_add=True)),
+            ],
+        ),
+        migrations.CreateModel(
+            name="Question",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("text", models.TextField()),
+                ("created_at", models.DateTimeField(auto_now_add=True)),
+                (
+                    "category",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE, to="quiz.category"
+                    ),
+                ),
+            ],
+        ),
+        migrations.CreateModel(
+            name="Option",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("text", models.CharField(max_length=255)),
+                ("is_correct", models.BooleanField(default=False)),
+                (
+                    "question",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="options",
+                        to="quiz.question",
+                    ),
+                ),
+            ],
+        ),
+        migrations.CreateModel(
+            name="QuizAttempt",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("score", models.IntegerField()),
+                ("completed", models.BooleanField(default=False)),
+                ("started_at", models.DateTimeField(auto_now_add=True)),
+                ("completed_at", models.DateTimeField(blank=True, null=True)),
+                (
+                    "category",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE, to="quiz.category"
+                    ),
+                ),
+                (
+                    "user",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to=settings.AUTH_USER_MODEL,
+                    ),
+                ),
+            ],
+        ),
+        migrations.CreateModel(
+            name="UserAnswer",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("is_correct", models.BooleanField(default=False)),
+                (
+                    "attempt",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="answers",
+                        to="quiz.quizattempt",
+                    ),
+                ),
+                (
+                    "question",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE, to="quiz.question"
+                    ),
+                ),
+                (
+                    "selected_option",
+                    models.ForeignKey(
+                        blank=True,
+                        null=True,
+                        on_delete=django.db.models.deletion.SET_NULL,
+                        to="quiz.option",
+                    ),
+                ),
+            ],
+        ),
+    ]
diff --git a/server/backend/quiz/tests/__init__.py b/server/backend/quiz/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/server/backend/quiz/tests/test_models.py b/server/backend/quiz/tests/test_models.py
new file mode 100644
index 00000000..69b3d004
--- /dev/null
+++ b/server/backend/quiz/tests/test_models.py
@@ -0,0 +1,49 @@
+import pytest
+from rest_framework.test import APIClient
+from rest_framework import status
+from django.contrib.auth import get_user_model
+from quiz.models import Category, Question, Option, QuizAttempt, UserAnswer
+
+@pytest.mark.django_db
+class TestQuizModels:
+
+    def test_category_model(self):
+        category = Category.objects.create(name="Math", description="Math related questions")
+        assert category.name == "Math"
+        assert category.description == "Math related questions"
+
+    def test_question_model(self):
+        category = Category.objects.create(name="Math", description="Math related questions")
+        question = Question.objects.create(category=category, text="What is 2+2?")
+        assert question.text == "What is 2+2?"
+        assert question.category == category
+
+    def test_option_model(self):
+        category = Category.objects.create(name="Math", description="Math related questions")
+        question = Question.objects.create(category=category, text="What is 2+2?")
+        option = Option.objects.create(question=question, text="4", is_correct=True)
+        assert option.text == "4"
+        assert option.is_correct is True
+
+    def test_quiz_attempt_model(self):
+        User = get_user_model()
+        user = User.objects.create_user(username="testuser", password="password123", email="testuser@example.com")
+        category = Category.objects.create(name="Math", description="Math related questions")
+        quiz_attempt = QuizAttempt.objects.create(user=user, category=category, score=80, completed=True)
+        assert quiz_attempt.user == user
+        assert quiz_attempt.category == category
+        assert quiz_attempt.score == 80
+        assert quiz_attempt.completed is True
+
+    def test_user_answer_model(self):
+        User = get_user_model()
+        user = User.objects.create_user(username="testuser", password="password123", email="testuser@example.com")
+        category = Category.objects.create(name="Math", description="Math related questions")
+        question = Question.objects.create(category=category, text="What is 2+2?")
+        option = Option.objects.create(question=question, text="4", is_correct=True)
+        quiz_attempt = QuizAttempt.objects.create(user=user, category=category, score=80, completed=True)
+        user_answer = UserAnswer.objects.create(attempt=quiz_attempt, question=question, selected_option=option, is_correct=True)
+        assert user_answer.attempt == quiz_attempt
+        assert user_answer.question == question
+        assert user_answer.selected_option == option
+        assert user_answer.is_correct is True
\ No newline at end of file
diff --git a/server/backend/quiz/tests/test_serializers.py b/server/backend/quiz/tests/test_serializers.py
new file mode 100644
index 00000000..e69de29b
diff --git a/server/backend/quiz/tests/test_views.py b/server/backend/quiz/tests/test_views.py
new file mode 100644
index 00000000..df413a91
--- /dev/null
+++ b/server/backend/quiz/tests/test_views.py
@@ -0,0 +1,81 @@
+import pytest
+from rest_framework.test import APIClient
+from rest_framework import status
+from users.models import User
+from quiz.models import Category, Question, Option, QuizAttempt, UserAnswer
+
+@pytest.mark.django_db
+class TestQuizViews:
+
+    client = APIClient()
+
+    def test_category_view_set(self):
+        category = Category.objects.create(name="Math", description="Math questions")
+        response = self.client.get(f'/quiz/')
+        assert response.status_code == status.HTTP_200_OK
+
+    def test_quiz_api_view(self):
+        category = Category.objects.create(name="Math", description="Math questions")
+        question1 = Question.objects.create(category=category, text="What is 2+2?")
+        question2 = Question.objects.create(category=category, text="What is 3+3?")
+        Option.objects.create(question=question1, text="4", is_correct=True)
+        Option.objects.create(question=question2, text="6", is_correct=True)
+
+        response = self.client.get(f'/quiz/{category.id}/')
+        assert response.status_code == status.HTTP_200_OK
+        assert len(response.data['questions']) == 2
+
+    def test_add_quiz_view(self):
+        user = User.objects.create_superuser(username="testuser", password="password123")
+        category_data = {"name": "Math", "description": "Math related questions"}
+        self.client.force_authenticate(user=user)
+        response = self.client.post('/quiz/add/', category_data, format='json')
+        assert response.status_code == status.HTTP_201_CREATED
+        assert response.data['name'] == "Math"
+
+    def test_add_question_view(self):
+        user = User.objects.create_superuser(username="testuser", password="password123")
+        category = Category.objects.create(name="Math", description="Math related questions")
+        payload = {
+            "category": category.id,
+            "text": "What is the capital of France?",
+            "options": [
+                {"text": "Paris", "is_correct": True},
+                {"text": "London", "is_correct": False},
+                {"text": "Berlin", "is_correct": False},
+                {"text": "Rome", "is_correct": False}
+            ]
+        }
+        self.client.force_authenticate(user=user)
+        response = self.client.post('/quiz/add-question/', payload, format='json')
+        assert response.status_code == status.HTTP_201_CREATED
+        assert response.data['text'] == "What is the capital of France?"
+
+    def test_delete_quiz_view(self):
+        user = User.objects.create_superuser(username="testuser", password="password123")
+        category = Category.objects.create(name="Math", description="Math questions")
+        self.client.force_authenticate(user=user)
+        response = self.client.delete('/quiz/delete/', data={"category_ids": [category.id]}, format='json')
+        assert response.status_code == status.HTTP_200_OK
+        assert "categories" in response.data["message"]
+
+    def test_update_quiz_view(self):
+        user = User.objects.create_superuser(username="testuser", password="password123")
+        category = Category.objects.create(name="Math", description="Math related questions")
+        updated_data = {"name": "Advanced Math"}
+        self.client.force_authenticate(user=user)
+        response = self.client.put(f'/quiz/update/{category.id}/', updated_data, format='json')
+        assert response.status_code == status.HTTP_200_OK
+        assert response.data['name'] == "Advanced Math"
+
+    def test_submit_quiz_view(self):
+        user = User.objects.create_user(username="testuser", password="password123")
+        category = Category.objects.create(name="Math", description="Math related questions")
+        question = Question.objects.create(category=category, text="What is 2+2?")
+        option = Option.objects.create(question=question, text="4", is_correct=True)
+        data = {"category_id": category.id, "answers": {str(question.id): option.id}}
+        self.client.force_authenticate(user=user)
+        response = self.client.post('/quiz/submit-quiz/', data, format='json')
+        assert response.status_code == status.HTTP_200_OK
+        assert response.data['score'] == 1
+        assert response.data['total'] == 1
diff --git a/server/backend/quiz/views.py b/server/backend/quiz/views.py
index cf3d417a..144ee16e 100644
--- a/server/backend/quiz/views.py
+++ b/server/backend/quiz/views.py
@@ -16,6 +16,7 @@ class CategoryViewSet(viewsets.ReadOnlyModelViewSet):
     serializer_class = CategorySerializer
 
 class QuizAPIView(APIView):
+    permission_classes = [AllowAny]
     def get(self, request, category_id):
         try:
             # Ensure category_id is valid
diff --git a/server/backend/users/migrations/0001_initial.py b/server/backend/users/migrations/0001_initial.py
new file mode 100644
index 00000000..7dce3fda
--- /dev/null
+++ b/server/backend/users/migrations/0001_initial.py
@@ -0,0 +1,187 @@
+# Generated by Django 5.1.4 on 2025-03-19 21:25
+
+import django.contrib.auth.models
+import django.contrib.auth.validators
+import django.db.models.deletion
+import django.utils.timezone
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    initial = True
+
+    dependencies = [
+        ("auth", "0012_alter_user_first_name_max_length"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="User",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("password", models.CharField(max_length=128, verbose_name="password")),
+                (
+                    "last_login",
+                    models.DateTimeField(
+                        blank=True, null=True, verbose_name="last login"
+                    ),
+                ),
+                (
+                    "is_superuser",
+                    models.BooleanField(
+                        default=False,
+                        help_text="Designates that this user has all permissions without explicitly assigning them.",
+                        verbose_name="superuser status",
+                    ),
+                ),
+                (
+                    "username",
+                    models.CharField(
+                        error_messages={
+                            "unique": "A user with that username already exists."
+                        },
+                        help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
+                        max_length=150,
+                        unique=True,
+                        validators=[
+                            django.contrib.auth.validators.UnicodeUsernameValidator()
+                        ],
+                        verbose_name="username",
+                    ),
+                ),
+                (
+                    "first_name",
+                    models.CharField(
+                        blank=True, max_length=150, verbose_name="first name"
+                    ),
+                ),
+                (
+                    "last_name",
+                    models.CharField(
+                        blank=True, max_length=150, verbose_name="last name"
+                    ),
+                ),
+                (
+                    "email",
+                    models.EmailField(
+                        blank=True, max_length=254, verbose_name="email address"
+                    ),
+                ),
+                (
+                    "is_staff",
+                    models.BooleanField(
+                        default=False,
+                        help_text="Designates whether the user can log into this admin site.",
+                        verbose_name="staff status",
+                    ),
+                ),
+                (
+                    "is_active",
+                    models.BooleanField(
+                        default=True,
+                        help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
+                        verbose_name="active",
+                    ),
+                ),
+                (
+                    "date_joined",
+                    models.DateTimeField(
+                        default=django.utils.timezone.now, verbose_name="date joined"
+                    ),
+                ),
+                (
+                    "role",
+                    models.CharField(
+                        choices=[("student", "Student"), ("instructor", "Instructor")],
+                        max_length=20,
+                    ),
+                ),
+                ("bio", models.TextField(blank=True, null=True)),
+                (
+                    "groups",
+                    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",
+                    ),
+                ),
+                (
+                    "user_permissions",
+                    models.ManyToManyField(
+                        blank=True,
+                        help_text="Specific permissions for this user.",
+                        related_name="user_set",
+                        related_query_name="user",
+                        to="auth.permission",
+                        verbose_name="user permissions",
+                    ),
+                ),
+            ],
+            options={
+                "verbose_name": "user",
+                "verbose_name_plural": "users",
+                "abstract": False,
+            },
+            managers=[
+                ("objects", django.contrib.auth.models.UserManager()),
+            ],
+        ),
+        migrations.CreateModel(
+            name="Instructor",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("designation", models.CharField(max_length=100)),
+                ("university", models.CharField(max_length=100)),
+                (
+                    "user",
+                    models.OneToOneField(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to=settings.AUTH_USER_MODEL,
+                    ),
+                ),
+            ],
+        ),
+        migrations.CreateModel(
+            name="Student",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("course_completed", models.IntegerField(default=0)),
+                ("course_enrolled", models.IntegerField(default=0)),
+                (
+                    "user",
+                    models.OneToOneField(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to=settings.AUTH_USER_MODEL,
+                    ),
+                ),
+            ],
+        ),
+    ]
diff --git a/server/backend/users/tests/test_models.py b/server/backend/users/tests/test_models.py
index 73b5e37b..c518df3f 100644
--- a/server/backend/users/tests/test_models.py
+++ b/server/backend/users/tests/test_models.py
@@ -1,69 +1,37 @@
 import pytest
-from users.models import Instructor, Student, User
+from users.models import User, Student, Instructor
 
 @pytest.mark.django_db
-class TestUserModel:
-    def test_user_creation(self):
-        user = User.objects.create_user(username="testuser", password="password123", role="student")
-        print(f"Created user with id: {user}")
-        assert user.username == "testuser"
-        assert user.role == "student"
-        assert user.check_password("password123")
-
-    def test_user_str(self):
-        user = User.objects.create_user(username="testuser", password="password123", role="student")
-        print(f"Created user with id: {user.id}")
-        assert str(user) == "testuser"
-
-
-@pytest.mark.django_db
-class TestInstructorModel:
-    def test_instructor_creation(self):
-        user = User.objects.create_user(username="test_instructor", password="password123", role="instructor")
-        print(f"Created user with id: {user.id}")
-        instructor = Instructor.objects.create(user=user, designation="Professor", university="MIT")
-        
-        assert instructor.user.username == "test_instructor"
-        assert instructor.designation == "Professor"
-        assert instructor.university == "MIT"
-
-#     def test_instructor_str(self):
-#         # Ensure no previous instructor exists
-#         Instructor.objects.all().delete()
-
-#         user = get_user_model().objects.create_user(
-#             username="instructor1", password="password123", role="instructor"
-#         )
-#         instructor = Instructor.objects.create(
-#             user=user, designation="Professor", university="ABC University"
-#         )
-#         assert str(instructor) == "instructor1"
-
-
-# @pytest.mark.django_db
-# class TestStudentModel:
-#     def test_student_creation(self):
-#         # Ensure no previous student exists
-#         Student.objects.all().delete()
-
-#         user = get_user_model().objects.create_user(
-#             username="student1", password="password123", role="student"
-#         )
-#         student = Student.objects.create(
-#             user=user, course_completed=3, course_enrolled=5
-#         )
-#         assert student.user.username == "student1"
-#         assert student.course_completed == 3
-#         assert student.course_enrolled == 5
-
-#     def test_student_str(self):
-#         # Ensure no previous student exists
-#         Student.objects.all().delete()
-
-#         user = get_user_model().objects.create_user(
-#             username="student1", password="password123", role="student"
-#         )
-#         student = Student.objects.create(
-#             user=user, course_completed=3, course_enrolled=5
-#         )
-#         assert str(student) == "student1"
+class TestUserModels:
+
+    def test_create_student_user(self):
+        user = User.objects.create_user(username='student1', password='testpass123', role='student')
+        print('user id', user.id)
+        print("\nNumber of users in database:", User.objects.count())        
+        student = Student.objects.get(user=user)        
+        student.course_completed = 2
+        student.course_enrolled = 5
+        student.save()
+        assert student.user == user
+        assert student.course_completed == 2
+        assert student.course_enrolled == 5
+        assert user.role == 'student'
+        assert student.user.username == 'student1'
+
+    def test_create_instructor_user(self):
+        user = User.objects.create_user(username='instructor1', password='testpass123', role='instructor')
+        print('user id', user.id)
+        print("\nNumber of users in database:", User.objects.count())        
+        instructor = Instructor.objects.get(user=user)        
+        instructor.designation = 'Professor'
+        instructor.university = 'University of Science'
+        instructor.save()
+        assert instructor.user == user
+        assert instructor.designation == 'Professor'
+        assert instructor.university == 'University of Science'
+        assert user.role == 'instructor'
+        assert instructor.user.username == 'instructor1'
+
+    def test_user_str_method(self):
+        user = User.objects.create_user(username='testuser', password='testpass123', role='student')        
+        assert str(user) == 'testuser'
diff --git a/server/backend/users/tests/test_serializers.py b/server/backend/users/tests/test_serializers.py
deleted file mode 100644
index 11ca96d0..00000000
--- a/server/backend/users/tests/test_serializers.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import pytest
-from rest_framework.exceptions import ValidationError
-from django.contrib.auth import get_user_model
-from users.models import Instructor
-from users.serializers import InstructorSerializer
-
-User = get_user_model()
-
-@pytest.fixture
-def create_instructor():
-    user = User.objects.create_user(username="john_doe", password="password123", role="instructor")
-    return Instructor.objects.create(user=user, designation="Professor", university="ABC University")
-
-@pytest.mark.django_db
-def test_instructor_serializer(create_instructor):
-    instructor = create_instructor
-    serializer = InstructorSerializer(instructor)
-    
-    # Check that the name field correctly pulls from the user model
-    assert serializer.data['name'] == instructor.user.username
-    assert serializer.data['designation'] == instructor.designation
-    assert serializer.data['university'] == instructor.university
-    assert set(serializer.data.keys()) == {'id', 'name', 'designation', 'university'}
-
-# @pytest.mark.django_db
-# def test_instructor_serializer_invalid_data():
-#     data = {
-#         "name": "Jane Doe",  # name should be a read-only field, so this would be invalid
-#         "designation": "Assistant Professor",
-#         "university": "XYZ University"
-#     }
-#     serializer = InstructorSerializer(data=data)
-#     with pytest.raises(ValidationError):
-#         serializer.is_valid(raise_exception=True)
diff --git a/server/backend/users/tests/test_serializers_auth.py b/server/backend/users/tests/test_serializers_auth.py
new file mode 100644
index 00000000..16032807
--- /dev/null
+++ b/server/backend/users/tests/test_serializers_auth.py
@@ -0,0 +1,84 @@
+import pytest
+from rest_framework.exceptions import ValidationError
+from rest_framework.test import APIClient
+from users.models import User, Instructor, Student
+from users.serializers import RegisterSerializer, LoginSerializer
+from django.contrib.auth import get_user_model
+
+@pytest.mark.django_db
+class TestUserSerializers:
+
+    def test_register_serializer(self):
+        user_data = {
+            'username': 'testuser',
+            'email': 'testuser@example.com',
+            'password': 'testpass123',
+            'role': 'student',
+            'bio': 'This is a test bio.',
+        }
+        serializer = RegisterSerializer(data=user_data)
+        assert serializer.is_valid()
+        user = serializer.save()
+        assert user.username == user_data['username']
+        assert user.email == user_data['email']
+        assert user.role == user_data['role']
+        assert user.bio == user_data['bio']
+        assert user.check_password(user_data['password'])
+        student = Student.objects.get(user=user)
+        assert student.course_completed == 0
+        assert student.course_enrolled == 0
+
+    def test_register_serializer_with_instructor(self):
+        user_data = {
+            'username': 'instructor1',
+            'email': 'instructor1@example.com',
+            'password': 'testpass123',
+            'role': 'instructor',
+            'bio': 'This is an instructor bio.',
+        }
+        serializer = RegisterSerializer(data=user_data)
+        assert serializer.is_valid()
+        user = serializer.save()
+        assert user.username == user_data['username']
+        assert user.email == user_data['email']
+        assert user.role == user_data['role']
+        assert user.bio == user_data['bio']
+        assert user.check_password(user_data['password'])
+        instructor = Instructor.objects.get(user=user)
+        assert instructor.designation == ''
+        assert instructor.university == ''
+
+    def test_register_serializer_invalid_role(self):
+        user_data = {
+            'username': 'invalidroleuser',
+            'email': 'invalidroleuser@example.com',
+            'password': 'testpass123',
+            'role': 'admin',
+            'bio': 'This is an invalid role bio.',
+        }
+        serializer = RegisterSerializer(data=user_data)
+        assert not serializer.is_valid()
+        assert 'role' in serializer.errors
+
+    def test_login_serializer_valid(self):
+        user = User.objects.create_user(username='testuser', password='testpass123', role='student', email='testuser@example.com')
+        login_data = {
+            'username': 'testuser',
+            'password': 'testpass123',
+        }
+        serializer = LoginSerializer(data=login_data)
+        assert serializer.is_valid()
+        response_data = serializer.validated_data
+        assert 'refresh' in response_data
+        assert 'access' in response_data
+        assert 'user' in response_data
+        assert response_data['user']['role'] == 'student'
+
+    def test_login_serializer_invalid_credentials(self):
+        login_data = {
+            'username': 'nonexistentuser',
+            'password': 'wrongpassword',
+        }
+        serializer = LoginSerializer(data=login_data)
+        with pytest.raises(ValidationError):
+            serializer.is_valid(raise_exception=True)
diff --git a/server/backend/users/tests/test_serializers_token_dashboard.py b/server/backend/users/tests/test_serializers_token_dashboard.py
new file mode 100644
index 00000000..a2de2e1d
--- /dev/null
+++ b/server/backend/users/tests/test_serializers_token_dashboard.py
@@ -0,0 +1,66 @@
+import pytest
+from rest_framework_simplejwt.tokens import RefreshToken, AccessToken
+from users.models import User
+from users.serializers import CustomTokenObtainPairSerializer, CustomTokenRefreshSerializer, DashboardStatsSerializer
+
+@pytest.mark.django_db
+class TestCustomTokenSerializers:
+
+    def test_custom_token_obtain_pair_serializer(self):
+        user = User.objects.create_user(username='testuser', password='testpass123', role='student', email='testuser@example.com')
+        serializer = CustomTokenObtainPairSerializer(data={'username': 'testuser', 'password': 'testpass123'})
+        assert serializer.is_valid()
+        
+        token = serializer.validated_data['access']
+        
+        # Decode the access token
+        decoded_token = AccessToken(token).payload
+        
+        # Assert that the custom fields are in the decoded token
+        assert 'role' in decoded_token
+        assert decoded_token['role'] == user.role
+        assert 'is_superuser' in decoded_token
+        assert decoded_token['is_superuser'] == user.is_superuser
+
+    def test_custom_token_refresh_serializer(self):
+        user = User.objects.create_user(username='testuser', password='testpass123', role='student', email='testuser@example.com')
+        refresh = RefreshToken.for_user(user)
+        data = {'refresh': str(refresh)}
+        serializer = CustomTokenRefreshSerializer(data=data)
+        assert serializer.is_valid()
+        
+        access_token = serializer.validated_data['access']
+        
+        # Decode the access token (not the refresh token)
+        decoded_token = AccessToken(access_token).payload
+        
+        # Assert that the custom fields are in the decoded token
+        assert 'role' in decoded_token
+        assert decoded_token['role'] == user.role
+        assert 'is_superuser' in decoded_token
+        assert decoded_token['is_superuser'] == user.is_superuser
+
+
+@pytest.mark.django_db
+class TestDashboardStatsSerializer:
+
+    def test_dashboard_stats_serializer(self):
+        user1 = User.objects.create_user(username='student1', password='testpass123', role='student', email='student1@example.com')
+        user2 = User.objects.create_user(username='instructor1', password='testpass123', role='instructor', email='instructor1@example.com')
+        course_data = {
+            'total_users': 2,
+            'total_students': 1,
+            'total_instructors': 1,
+            'total_courses': 0,
+            'total_contents': 0,
+            'total_quizzes': 0,
+        }
+        serializer = DashboardStatsSerializer(data=course_data)
+        assert serializer.is_valid()
+        stats = serializer.validated_data
+        assert stats['total_users'] == 2
+        assert stats['total_students'] == 1
+        assert stats['total_instructors'] == 1
+        assert stats['total_courses'] == 0
+        assert stats['total_contents'] == 0
+        assert stats['total_quizzes'] == 0
diff --git a/server/backend/users/tests/test_serializers_user.py b/server/backend/users/tests/test_serializers_user.py
new file mode 100644
index 00000000..8a233ef6
--- /dev/null
+++ b/server/backend/users/tests/test_serializers_user.py
@@ -0,0 +1,43 @@
+import pytest
+from rest_framework.exceptions import ValidationError
+from users.models import User, Instructor
+from users.serializers import UserSerializer, InstructorSerializer
+
+@pytest.mark.django_db
+class TestUserSerializers:
+
+    def test_user_serializer(self):
+        user = User.objects.create_user(username='testuser', password='testpass123', role='student', email='testuser@example.com', bio='This is a test bio.')
+        serializer = UserSerializer(user)
+        assert serializer.data['id'] == user.id
+        assert serializer.data['username'] == user.username
+        assert serializer.data['email'] == user.email
+        assert serializer.data['role'] == user.role
+        assert serializer.data['bio'] == user.bio
+        assert serializer.data['is_superuser'] == user.is_superuser
+
+    def test_instructor_serializer(self):
+        user = User.objects.create_user(username='instructor1', password='testpass123', role='instructor', email='instructor1@example.com')
+        instructor = Instructor.objects.get(user=user)
+        serializer = InstructorSerializer(instructor)
+        assert serializer.data['id'] == instructor.id
+        assert serializer.data['name'] == user.username
+        assert serializer.data['designation'] == instructor.designation
+        assert serializer.data['university'] == instructor.university
+
+    def test_instructor_serializer_missing_user(self):
+        instructor = Instructor(designation='Professor', university='University of Science')
+        try:
+            InstructorSerializer(instructor).data
+        except ValidationError as e:
+            assert 'user' in str(e)
+
+    def test_user_serializer_invalid_data(self):
+        invalid_data = {
+            'username': '',
+            'email': 'invalidemail',
+        }
+        serializer = UserSerializer(data=invalid_data)
+        assert not serializer.is_valid()
+        assert 'username' in serializer.errors
+        assert 'email' in serializer.errors
diff --git a/server/backend/users/tests/test_views.py b/server/backend/users/tests/test_views.py
new file mode 100644
index 00000000..90ba1cf9
--- /dev/null
+++ b/server/backend/users/tests/test_views.py
@@ -0,0 +1,93 @@
+import pytest
+from rest_framework.test import APIClient
+from rest_framework_simplejwt.tokens import RefreshToken
+from rest_framework import status
+from users.models import User
+@pytest.mark.django_db
+class TestAuthViews:
+    client = APIClient()
+    def test_register_view(self):
+        data = {
+            'username': 'testuser',
+            'email': 'testuser@example.com',
+            'password': 'password123',
+            'role': 'student',
+        }
+        response = self.client.post('/register/', data, format='json')        
+        assert response.status_code == status.HTTP_201_CREATED
+        assert 'user' in response.data
+        assert 'refresh' in response.data
+        assert 'access' in response.data
+        user = User.objects.get(username='testuser')
+        assert user.email == 'testuser@example.com'
+
+    def test_login_view(self):
+        data = {
+            'username': 'testuser',
+            'password': 'password123',
+        }
+        user = User.objects.create_user(username='testuser', password='password123', email='testuser@example.com', role='student')
+
+        response = self.client.post('/login/', data, format='json')
+
+        assert response.status_code == status.HTTP_200_OK
+        assert 'refresh' in response.data
+        assert 'access' in response.data
+        assert 'user' in response.data
+
+    def test_logout_view(self):
+        user = User.objects.create_user(username='testuser', password='password123', email='testuser@example.com', role='student')
+        
+        # Generate refresh token
+        refresh_token = str(RefreshToken.for_user(user))
+        response = self.client.post('/logout/', {'refresh': refresh_token}, format='json')
+        
+        assert response.status_code == status.HTTP_205_RESET_CONTENT
+        assert response.data.get("message") == "Logout successful"
+
+    def test_logout_view_no_token(self):
+        response = self.client.post('/logout/', {}, format='json')
+        
+        assert response.status_code == status.HTTP_400_BAD_REQUEST
+        assert response.data.get("error") == "Refresh token is required"
+
+    def test_dashboard_stats_view(self):
+        admin_user = User.objects.create_superuser(username='adminuser', password='password123', email='admin@example.com')        
+        self.client.force_authenticate(user=admin_user)
+
+        response = self.client.get('/dashboard/stats/', format='json')
+        
+        assert response.status_code == status.HTTP_200_OK
+        assert 'success' in response.data
+        assert response.data.get('success') is True
+        assert 'data' in response.data
+        assert 'total_users' in response.data['data']
+        assert 'total_students' in response.data['data']
+
+    def test_dashboard_stats_view_no_permission(self):
+        normal_user = User.objects.create_user(username='normaluser', password='password123', email='normal@example.com')
+        self.client.force_authenticate(user=normal_user)
+
+        response = self.client.get('/dashboard/stats/', format='json')
+        
+        assert response.status_code == status.HTTP_403_FORBIDDEN
+        assert 'detail' in response.data
+        assert response.data['detail'] == 'You do not have permission to perform this action.'
+
+@pytest.mark.django_db
+class TestInstructorViewSet:
+
+    def test_instructor_viewset(self) -> None:
+        user = User.objects.create_user(username='instructoruser', password='password123', email='instructor@example.com', role='instructor')
+        # No need to store instructor reference if not used
+
+        client = APIClient()
+        # Optional: Authenticate with the instructor
+        # client.force_authenticate(user=user)
+        response = client.get('/instructors/', format='json')
+
+        assert response.status_code == status.HTTP_200_OK
+        assert len(response.data) > 0
+        assert 'id' in response.data[0]
+        assert 'name' in response.data[0]
+        assert response.data[0]['name'] == user.username
diff --git a/sonar-project.properties b/sonar-project.properties
index 10643858..1aa13c7f 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -5,8 +5,8 @@ sonar.organization=learnathon-by-geeky-solutions
 sonar.host.url=https://sonarcloud.io
 
 # Tell SonarCloud which folder contains your real code
-sonar.sources=frontend/,server/
-sonar.tests=server/backend
+sonar.sources=frontend/,server/backend
+sonar.tests=server/backend/courses,server/backend/users
 sonar.test.inclusions=server/**/tests/**/*.py
 
 # Exclude unnecessary files from coverage calculation


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 35**

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 8fc7402bf25f7bdbb05a24f6b3ce2546b93a013a and 22ad127854980b3aa130e79405ea86ef2db4d651.

</details>

<details>
<summary>📒 Files selected for processing (22)</summary>

* `.github/workflows/sonarqube.yml` (2 hunks)
* `server/.gitignore` (1 hunks)
* `server/backend/backend/settings.py` (3 hunks)
* `server/backend/courses/migrations/0001_initial.py` (1 hunks)
* `server/backend/courses/migrations/0002_course_image_alter_rating_rating.py` (1 hunks)
* `server/backend/coverage.xml` (1 hunks)
* `server/backend/forum/migrations/0001_initial.py` (1 hunks)
* `server/backend/forum/serializers.py` (0 hunks)
* `server/backend/forum/tests/test_models.py` (1 hunks)
* `server/backend/forum/views.py` (0 hunks)
* `server/backend/quiz/migrations/0001_initial.py` (1 hunks)
* `server/backend/quiz/tests/test_models.py` (1 hunks)
* `server/backend/quiz/tests/test_views.py` (1 hunks)
* `server/backend/quiz/views.py` (1 hunks)
* `server/backend/users/migrations/0001_initial.py` (1 hunks)
* `server/backend/users/tests/test_models.py` (1 hunks)
* `server/backend/users/tests/test_serializers.py` (0 hunks)
* `server/backend/users/tests/test_serializers_auth.py` (1 hunks)
* `server/backend/users/tests/test_serializers_token_dashboard.py` (1 hunks)
* `server/backend/users/tests/test_serializers_user.py` (1 hunks)
* `server/backend/users/tests/test_views.py` (1 hunks)
* `sonar-project.properties` (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (3)</summary>

* server/backend/forum/views.py
* server/backend/forum/serializers.py
* server/backend/users/tests/test_serializers.py

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (10)</summary>

<details>
<summary>server/backend/courses/migrations/0002_course_image_alter_rating_rating.py (1)</summary><blockquote>

<details>
<summary>server/backend/courses/migrations/0001_initial.py (1)</summary>

* `Migration` (8-157)

</details>

</blockquote></details>
<details>
<summary>server/backend/users/tests/test_serializers_auth.py (2)</summary><blockquote>

<details>
<summary>server/backend/users/models.py (3)</summary>

* `User` (4-13)
* `Instructor` (15-21)
* `Student` (23-29)

</details>
<details>
<summary>server/backend/users/serializers.py (2)</summary>

* `RegisterSerializer` (23-46)
* `LoginSerializer` (48-65)

</details>

</blockquote></details>
<details>
<summary>server/backend/quiz/tests/test_views.py (2)</summary><blockquote>

<details>
<summary>server/backend/quiz/models.py (5)</summary>

* `Category` (4-14)
* `Question` (16-22)
* `Option` (24-30)
* `QuizAttempt` (32-41)
* `UserAnswer` (43-50)

</details>
<details>
<summary>server/backend/quiz/views.py (5)</summary>

* `get` (20-39)
* `post` (44-51)
* `post` (56-62)
* `post` (95-174)
* `delete` (67-79)

</details>

</blockquote></details>
<details>
<summary>server/backend/users/migrations/0001_initial.py (2)</summary><blockquote>

<details>
<summary>server/backend/forum/migrations/0001_initial.py (1)</summary>

* `Migration` (9-143)

</details>
<details>
<summary>server/backend/quiz/migrations/0001_initial.py (1)</summary>

* `Migration` (8-148)

</details>

</blockquote></details>
<details>
<summary>server/backend/quiz/migrations/0001_initial.py (3)</summary><blockquote>

<details>
<summary>server/backend/courses/migrations/0001_initial.py (1)</summary>

* `Migration` (8-157)

</details>
<details>
<summary>server/backend/forum/migrations/0001_initial.py (1)</summary>

* `Migration` (9-143)

</details>
<details>
<summary>server/backend/users/migrations/0001_initial.py (1)</summary>

* `Migration` (11-187)

</details>

</blockquote></details>
<details>
<summary>server/backend/forum/tests/test_models.py (1)</summary><blockquote>

<details>
<summary>server/backend/forum/models.py (3)</summary>

* `Tag` (5-8)
* `Vote` (40-47)
* `total_votes` (20-21)

</details>

</blockquote></details>
<details>
<summary>server/backend/users/tests/test_serializers_token_dashboard.py (2)</summary><blockquote>

<details>
<summary>server/backend/users/models.py (1)</summary>

* `User` (4-13)

</details>
<details>
<summary>server/backend/users/serializers.py (3)</summary>

* `CustomTokenObtainPairSerializer` (70-77)
* `CustomTokenRefreshSerializer` (79-94)
* `DashboardStatsSerializer` (98-106)

</details>

</blockquote></details>
<details>
<summary>server/backend/quiz/tests/test_models.py (1)</summary><blockquote>

<details>
<summary>server/backend/quiz/models.py (5)</summary>

* `Category` (4-14)
* `Question` (16-22)
* `Option` (24-30)
* `QuizAttempt` (32-41)
* `UserAnswer` (43-50)

</details>

</blockquote></details>
<details>
<summary>server/backend/users/tests/test_serializers_user.py (2)</summary><blockquote>

<details>
<summary>server/backend/users/models.py (2)</summary>

* `User` (4-13)
* `Instructor` (15-21)

</details>
<details>
<summary>server/backend/users/serializers.py (2)</summary>

* `UserSerializer` (16-19)
* `InstructorSerializer` (9-14)

</details>

</blockquote></details>
<details>
<summary>server/backend/users/tests/test_views.py (1)</summary><blockquote>

<details>
<summary>server/backend/users/models.py (2)</summary>

* `Instructor` (15-21)
* `User` (4-13)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Ruff (0.8.2)</summary>

<details>
<summary>server/backend/courses/migrations/0002_course_image_alter_rating_rating.py</summary>

8-10: Mutable class attributes should be annotated with `typing.ClassVar`

(RUF012)

---

12-28: Mutable class attributes should be annotated with `typing.ClassVar`

(RUF012)

---

25-25: Trailing comma missing

Add trailing comma

(COM812)

</details>
<details>
<summary>server/backend/backend/settings.py</summary>

110-110: Trailing comma missing

Add trailing comma

(COM812)

---

121-121: Trailing comma missing

Add trailing comma

(COM812)

</details>
<details>
<summary>server/backend/users/tests/test_serializers_auth.py</summary>

3-3: `rest_framework.test.APIClient` imported but unused

Remove unused import: `rest_framework.test.APIClient`

(F401)

---

6-6: `django.contrib.auth.get_user_model` imported but unused

Remove unused import: `django.contrib.auth.get_user_model`

(F401)

---

11-11: Missing return type annotation for public function `test_register_serializer`

Add return type annotation: `None`

(ANN201)

---

31-31: Missing return type annotation for public function `test_register_serializer_with_instructor`

Add return type annotation: `None`

(ANN201)

---

51-51: Missing return type annotation for public function `test_register_serializer_invalid_role`

Add return type annotation: `None`

(ANN201)

---

63-63: Missing return type annotation for public function `test_login_serializer_valid`

Add return type annotation: `None`

(ANN201)

---

64-64: Local variable `user` is assigned to but never used

Remove assignment to unused variable `user`

(F841)

---

64-64: Possible hardcoded password assigned to argument: "password"

(S106)

---

77-77: Missing return type annotation for public function `test_login_serializer_invalid_credentials`

Add return type annotation: `None`

(ANN201)

</details>
<details>
<summary>server/backend/quiz/tests/test_views.py</summary>

5-5: `quiz.models.QuizAttempt` imported but unused

Remove unused import

(F401)

---

5-5: `quiz.models.UserAnswer` imported but unused

Remove unused import

(F401)

---

12-12: Missing return type annotation for public function `test_category_view_set`

Add return type annotation: `None`

(ANN201)

---

13-13: Local variable `category` is assigned to but never used

Remove assignment to unused variable `category`

(F841)

---

14-14: f-string without any placeholders

Remove extraneous `f` prefix

(F541)

---

17-17: Missing return type annotation for public function `test_quiz_api_view`

Add return type annotation: `None`

(ANN201)

---

28-28: Missing return type annotation for public function `test_add_quiz_view`

Add return type annotation: `None`

(ANN201)

---

29-29: Possible hardcoded password assigned to argument: "password"

(S106)

---

36-36: Missing return type annotation for public function `test_add_question_view`

Add return type annotation: `None`

(ANN201)

---

37-37: Possible hardcoded password assigned to argument: "password"

(S106)

---

46-46: Trailing comma missing

Add trailing comma

(COM812)

---

47-47: Trailing comma missing

Add trailing comma

(COM812)

---

54-54: Missing return type annotation for public function `test_delete_quiz_view`

Add return type annotation: `None`

(ANN201)

---

55-55: Possible hardcoded password assigned to argument: "password"

(S106)

---

62-62: Missing return type annotation for public function `test_update_quiz_view`

Add return type annotation: `None`

(ANN201)

---

63-63: Possible hardcoded password assigned to argument: "password"

(S106)

---

71-71: Missing return type annotation for public function `test_submit_quiz_view`

Add return type annotation: `None`

(ANN201)

---

72-72: Possible hardcoded password assigned to argument: "password"

(S106)

</details>
<details>
<summary>server/backend/users/migrations/0001_initial.py</summary>

14-16: Mutable class attributes should be annotated with `typing.ClassVar`

(RUF012)

---

18-187: Mutable class attributes should be annotated with `typing.ClassVar`

(RUF012)

---

35-35: Trailing comma missing

Add trailing comma

(COM812)

---

50-50: Trailing comma missing

Add trailing comma

(COM812)

---

56-56: Trailing comma missing

Add trailing comma

(COM812)

---

64-64: Trailing comma missing

Add trailing comma

(COM812)

---

70-70: Trailing comma missing

Add trailing comma

(COM812)

---

76-76: Trailing comma missing

Add trailing comma

(COM812)

---

98-98: Trailing comma missing

Add trailing comma

(COM812)

</details>
<details>
<summary>server/backend/quiz/views.py</summary>

19-19: Mutable class attributes should be annotated with `typing.ClassVar`

(RUF012)

</details>
<details>
<summary>server/backend/users/tests/test_models.py</summary>

7-7: Missing return type annotation for public function `test_create_student_user`

Add return type annotation: `None`

(ANN201)

---

8-8: Possible hardcoded password assigned to argument: "password"

(S106)

---

21-21: Missing return type annotation for public function `test_create_instructor_user`

Add return type annotation: `None`

(ANN201)

---

22-22: Possible hardcoded password assigned to argument: "password"

(S106)

---

35-35: Missing return type annotation for public function `test_user_str_method`

Add return type annotation: `None`

(ANN201)

---

36-36: Possible hardcoded password assigned to argument: "password"

(S106)

</details>
<details>
<summary>server/backend/quiz/migrations/0001_initial.py</summary>

11-13: Mutable class attributes should be annotated with `typing.ClassVar`

(RUF012)

---

15-148: Mutable class attributes should be annotated with `typing.ClassVar`

(RUF012)

---

50-50: Trailing comma missing

Add trailing comma

(COM812)

---

98-98: Trailing comma missing

Add trailing comma

(COM812)

---

134-134: Trailing comma missing

Add trailing comma

(COM812)

</details>
<details>
<summary>server/backend/forum/migrations/0001_initial.py</summary>

12-14: Mutable class attributes should be annotated with `typing.ClassVar`

(RUF012)

---

16-143: Mutable class attributes should be annotated with `typing.ClassVar`

(RUF012)

</details>
<details>
<summary>server/backend/forum/tests/test_models.py</summary>

8-8: Missing return type annotation for public function `test_tag_creation`

Add return type annotation: `None`

(ANN201)

---

13-13: Missing return type annotation for public function `test_post_creation`

Add return type annotation: `None`

(ANN201)

---

14-14: Possible hardcoded password assigned to argument: "password"

(S106)

---

25-25: Missing return type annotation for public function `test_comment_creation`

Add return type annotation: `None`

(ANN201)

---

26-26: Possible hardcoded password assigned to argument: "password"

(S106)

---

35-35: Missing return type annotation for public function `test_comment_reply`

Add return type annotation: `None`

(ANN201)

---

36-36: Possible hardcoded password assigned to argument: "password"

(S106)

---

44-44: Missing return type annotation for public function `test_vote_creation`

Add return type annotation: `None`

(ANN201)

---

45-45: Possible hardcoded password assigned to argument: "password"

(S106)

---

53-53: Missing return type annotation for public function `test_total_votes`

Add return type annotation: `None`

(ANN201)

---

54-54: Possible hardcoded password assigned to argument: "password"

(S106)

---

55-55: Possible hardcoded password assigned to argument: "password"

(S106)

</details>
<details>
<summary>server/backend/courses/migrations/0001_initial.py</summary>

11-14: Mutable class attributes should be annotated with `typing.ClassVar`

(RUF012)

---

16-157: Mutable class attributes should be annotated with `typing.ClassVar`

(RUF012)

---

108-108: Trailing comma missing

Add trailing comma

(COM812)

</details>
<details>
<summary>server/backend/users/tests/test_serializers_token_dashboard.py</summary>

9-9: Missing return type annotation for public function `test_custom_token_obtain_pair_serializer`

Add return type annotation: `None`

(ANN201)

---

10-10: Possible hardcoded password assigned to argument: "password"

(S106)

---

25-25: Missing return type annotation for public function `test_custom_token_refresh_serializer`

Add return type annotation: `None`

(ANN201)

---

26-26: Possible hardcoded password assigned to argument: "password"

(S106)

---

47-47: Missing return type annotation for public function `test_dashboard_stats_serializer`

Add return type annotation: `None`

(ANN201)

---

48-48: Local variable `user1` is assigned to but never used

Remove assignment to unused variable `user1`

(F841)

---

48-48: Possible hardcoded password assigned to argument: "password"

(S106)

---

49-49: Local variable `user2` is assigned to but never used

Remove assignment to unused variable `user2`

(F841)

---

49-49: Possible hardcoded password assigned to argument: "password"

(S106)

</details>
<details>
<summary>server/backend/quiz/tests/test_models.py</summary>

2-2: `rest_framework.test.APIClient` imported but unused

Remove unused import: `rest_framework.test.APIClient`

(F401)

---

3-3: `rest_framework.status` imported but unused

Remove unused import: `rest_framework.status`

(F401)

---

10-10: Missing return type annotation for public function `test_category_model`

Add return type annotation: `None`

(ANN201)

---

15-15: Missing return type annotation for public function `test_question_model`

Add return type annotation: `None`

(ANN201)

---

21-21: Missing return type annotation for public function `test_option_model`

Add return type annotation: `None`

(ANN201)

---

28-28: Missing return type annotation for public function `test_quiz_attempt_model`

Add return type annotation: `None`

(ANN201)

---

30-30: Possible hardcoded password assigned to argument: "password"

(S106)

---

38-38: Missing return type annotation for public function `test_user_answer_model`

Add return type annotation: `None`

(ANN201)

---

40-40: Possible hardcoded password assigned to argument: "password"

(S106)

</details>
<details>
<summary>server/backend/users/tests/test_serializers_user.py</summary>

9-9: Missing return type annotation for public function `test_user_serializer`

Add return type annotation: `None`

(ANN201)

---

10-10: Possible hardcoded password assigned to argument: "password"

(S106)

---

19-19: Missing return type annotation for public function `test_instructor_serializer`

Add return type annotation: `None`

(ANN201)

---

20-20: Possible hardcoded password assigned to argument: "password"

(S106)

---

28-28: Missing return type annotation for public function `test_instructor_serializer_missing_user`

Add return type annotation: `None`

(ANN201)

---

31-31: Found useless attribute access. Either assign it to a variable or remove it.

(B018)

---

33-33: Found assertion on exception `e` in `except` block, use `pytest.raises()` instead

(PT017)

---

35-35: Missing return type annotation for public function `test_user_serializer_invalid_data`

Add return type annotation: `None`

(ANN201)

</details>
<details>
<summary>server/backend/users/tests/test_views.py</summary>

10-10: Missing return type annotation for public function `test_register_view`

Add return type annotation: `None`

(ANN201)

---

25-25: Missing return type annotation for public function `test_login_view`

Add return type annotation: `None`

(ANN201)

---

30-30: Local variable `user` is assigned to but never used

Remove assignment to unused variable `user`

(F841)

---

30-30: Possible hardcoded password assigned to argument: "password"

(S106)

---

39-39: Missing return type annotation for public function `test_logout_view`

Add return type annotation: `None`

(ANN201)

---

40-40: Possible hardcoded password assigned to argument: "password"

(S106)

---

49-49: Missing return type annotation for public function `test_logout_view_no_token`

Add return type annotation: `None`

(ANN201)

---

55-55: Missing return type annotation for public function `test_dashboard_stats_view`

Add return type annotation: `None`

(ANN201)

---

56-56: Possible hardcoded password assigned to argument: "password"

(S106)

---

68-68: Missing return type annotation for public function `test_dashboard_stats_view_no_permission`

Add return type annotation: `None`

(ANN201)

---

69-69: Possible hardcoded password assigned to argument: "password"

(S106)

---

81-81: Missing return type annotation for public function `test_instructor_viewset`

Add return type annotation: `None`

(ANN201)

---

82-82: Possible hardcoded password assigned to argument: "password"

(S106)

---

83-83: Local variable `instructor` is assigned to but never used

Remove assignment to unused variable `instructor`

(F841)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (43)</summary><blockquote>

<details>
<summary>server/backend/coverage.xml (1)</summary>

`2-2`: **TIMESTAMP UPDATE DETECTED. NO ACTION REQUIRED.**

OBSERVED MODIFICATION: COVERAGE REPORT TIMESTAMP UPDATED.
THIS IS EXPECTED BEHAVIOR WHEN REGENERATING COVERAGE REPORTS.

</details>
<details>
<summary>server/backend/quiz/views.py (1)</summary>

`19-19`: <details>
<summary>❓ Verification inconclusive</summary>

**SECURITY CONFIGURATION ALTERED. VERIFICATION NEEDED.**

PERMISSION CONFIGURATION MODIFIED TO ALLOW ANONYMOUS ACCESS TO QUIZ API. 
THIS INTRODUCES POTENTIAL SECURITY IMPLICATIONS IF QUIZ DATA SHOULD BE RESTRICTED.

ALSO DETECTED: CODE STYLE INCONSISTENCY. STATIC ANALYSIS TOOL REPORTS MUTABLE CLASS ATTRIBUTE SHOULD USE TYPING.CLASSVAR.



```diff
-    permission_classes = [AllowAny]
+    permission_classes: ClassVar = [AllowAny]
```

EXECUTE VERIFICATION SCRIPT TO ANALYZE AUTHENTICATION REQUIREMENTS:

---

🏁 Script executed:

```shell
#!/bin/bash
# Checking if QuizAPIView is intended to be public by examining tests and other views
echo "Analyzing authentication patterns..."
echo "Checking test files for QuizAPIView authentication expectations:"
rg -A 5 "QuizAPIView" server/backend/quiz/tests/
echo "Comparing with other views permission patterns:"
rg -A 1 "permission_classes" server/backend/quiz/
echo "Checking if unauthenticated access is tested:"
rg -A 5 "test.*quiz.*without.*auth|test.*unauthenticated.*quiz" server/backend/quiz/tests/
```

Length of output: 1486

---



</details>

**ACTION REQUIRED: Verify Security Intent & Code Style Consistency on Quiz API**  
- The change to annotate the mutable class attribute with `ClassVar` correctly resolves the static analysis code style warning.  
- However, the permission setting remains as `[AllowAny]`, which allows anonymous access to the Quiz API. Our verification script did not find tests confirming whether this open access is intentional.  
- Please manually verify if the Quiz API is designed to be public. If so, consider adding tests to document this behavior; if not, update the permission configuration accordingly.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

19-19: Mutable class attributes should be annotated with `typing.ClassVar`

(RUF012)

</details>

</details>

</details>
<details>
<summary>server/.gitignore (1)</summary>

`13-14`: **POSITIVE MODIFICATION DETECTED: VERSION CONTROL STRATEGY IMPROVED.**

GITIGNORE UPDATED TO TRACK MIGRATION FILES. THIS FOLLOWS RECOMMENDED DJANGO PRACTICES.

ANALYSIS: TRACKING MIGRATIONS IN VERSION CONTROL ENSURES CONSISTENT DATABASE SCHEMA ACROSS ALL ENVIRONMENTS AND DEVELOPERS.

</details>
<details>
<summary>server/backend/courses/migrations/0002_course_image_alter_rating_rating.py (3)</summary>

`1-2`: **Beep Boop...** These lines are automatically generated by Django. No issues detected.

---

`3-4`: **Beep Boop...** Imports look correct.

---

`12-17`: **Beep Boop...** Addition of the `image` field is logical for optional course images. Implementation is consistent with Django patterns.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

12-28: Mutable class attributes should be annotated with `typing.ClassVar`

(RUF012)

</details>

</details>

</details>
<details>
<summary>.github/workflows/sonarqube.yml (5)</summary>

`6-6`: **Beep Boop...** Targeting the `main` branch for pushes is typical.

---

`9-9`: **Beep Boop...** Ensuring PRs also target `main` is consistent.

---

`23-23`: **Beep Boop...** Upgrading to Python 3.10 is recommended for new features and improvements.

---

`28-28`: **Beep Boop...** Installing dependencies from `./server/requirements.txt` is correct if that file is properly maintained.

---

`48-48`: **Beep Boop...** Using `pytest server/backend --cov=backend` is correct to generate coverage.

</details>
<details>
<summary>server/backend/backend/settings.py (2)</summary>

`15-15`: **Beep Boop...** Importing `os` is necessary for environment variable checks.

---

`183-183`: **Beep Boop...** `SIMPLE_JWT` dict updated? No issues found.

</details>
<details>
<summary>server/backend/users/migrations/0001_initial.py (5)</summary>

`1-2`: **Beep Boop...** Django auto-generated comments. All good.

---

`3-8`: **Beep Boop...** Imports for Django’s auth, validators, DB modules are correct.

---

`11-16`: **Beep Boop...** Standard initial migration setup with `initial = True`.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

14-16: Mutable class attributes should be annotated with `typing.ClassVar`

(RUF012)

</details>

</details>

---

`141-163`: **Beep Boop...** `Instructor` model references the extended `User` model via OneToOne. Perfectly standard.

---

`164-186`: **Beep Boop...** `Student` model references the extended `User` model via OneToOne. Also consistent.

</details>
<details>
<summary>server/backend/forum/migrations/0001_initial.py (1)</summary>

`1-144`: **DATABASE SCHEMA INITIALIZATION COMPLETE.**

THIS MIGRATION FILE ESTABLISHES INITIAL DATABASE SCHEMA FOR FORUM APPLICATION. MODELS INCLUDE:
- TAG: WITH UNIQUE NAME FIELD
- POST: WITH AUTHOR RELATIONSHIP AND MANY-TO-MANY TAG CONNECTION
- COMMENT: WITH HIERARCHICAL STRUCTURE USING MPTT
- VOTE: WITH UPVOTE/DOWNVOTE OPTIONS AND UNIQUE USER-POST CONSTRAINT

SCHEMA DESIGN FOLLOWS OPTIMAL DATABASE PRACTICES. NO ISSUES DETECTED.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

12-14: Mutable class attributes should be annotated with `typing.ClassVar`

(RUF012)

---

16-143: Mutable class attributes should be annotated with `typing.ClassVar`

(RUF012)

</details>

</details>

</details>
<details>
<summary>server/backend/quiz/tests/test_views.py (1)</summary>

`12-81`: **TEST SUITE FUNCTIONS CORRECTLY. GOOD COVERAGE OF VIEW FUNCTIONALITY.**

TEST METHODS EFFECTIVELY VERIFY:
- CATEGORY VIEW RETRIEVAL
- QUIZ API RESPONSES
- ADMIN OPERATIONS (ADD/DELETE/UPDATE)
- QUIZ SUBMISSION PROCESS

ALL TESTS FOLLOW CONSISTENT PATTERN AND VERIFY KEY FUNCTIONALITY. AUTHENTICATION IS PROPERLY TESTED WHERE REQUIRED.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

12-12: Missing return type annotation for public function `test_category_view_set`

Add return type annotation: `None`

(ANN201)

---

13-13: Local variable `category` is assigned to but never used

Remove assignment to unused variable `category`

(F841)

---

14-14: f-string without any placeholders

Remove extraneous `f` prefix

(F541)

---

17-17: Missing return type annotation for public function `test_quiz_api_view`

Add return type annotation: `None`

(ANN201)

---

28-28: Missing return type annotation for public function `test_add_quiz_view`

Add return type annotation: `None`

(ANN201)

---

29-29: Possible hardcoded password assigned to argument: "password"

(S106)

---

36-36: Missing return type annotation for public function `test_add_question_view`

Add return type annotation: `None`

(ANN201)

---

37-37: Possible hardcoded password assigned to argument: "password"

(S106)

---

46-46: Trailing comma missing

Add trailing comma

(COM812)

---

47-47: Trailing comma missing

Add trailing comma

(COM812)

---

54-54: Missing return type annotation for public function `test_delete_quiz_view`

Add return type annotation: `None`

(ANN201)

---

55-55: Possible hardcoded password assigned to argument: "password"

(S106)

---

62-62: Missing return type annotation for public function `test_update_quiz_view`

Add return type annotation: `None`

(ANN201)

---

63-63: Possible hardcoded password assigned to argument: "password"

(S106)

---

71-71: Missing return type annotation for public function `test_submit_quiz_view`

Add return type annotation: `None`

(ANN201)

---

72-72: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

</details>
<details>
<summary>server/backend/forum/tests/test_models.py (3)</summary>

`53-62`: **TEST VALIDATES NET VOTES CALCULATION CORRECTLY.**

GOOD TEST CASE FOR `total_votes()` FUNCTION. TEST CREATES BALANCED UPVOTE AND DOWNVOTE SCENARIO AND VERIFIES EXPECTED RESULT IS ZERO.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

53-53: Missing return type annotation for public function `test_total_votes`

Add return type annotation: `None`

(ANN201)

---

54-54: Possible hardcoded password assigned to argument: "password"

(S106)

---

55-55: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

---

`7-62`: **COMPREHENSIVE MODEL TESTS DETECTED. ALL FORUM MODELS VERIFIED.**

TESTS COVER:
- MODEL CREATION
- RELATIONSHIPS
- STRING REPRESENTATIONS
- HIERARCHICAL COMMENT STRUCTURE 
- VOTING MECHANISM

TESTS ARE CONCISE AND FOCUS ON ESSENTIAL FUNCTIONALITY. TEST SUITE PERFORMS WELL.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

8-8: Missing return type annotation for public function `test_tag_creation`

Add return type annotation: `None`

(ANN201)

---

13-13: Missing return type annotation for public function `test_post_creation`

Add return type annotation: `None`

(ANN201)

---

14-14: Possible hardcoded password assigned to argument: "password"

(S106)

---

25-25: Missing return type annotation for public function `test_comment_creation`

Add return type annotation: `None`

(ANN201)

---

26-26: Possible hardcoded password assigned to argument: "password"

(S106)

---

35-35: Missing return type annotation for public function `test_comment_reply`

Add return type annotation: `None`

(ANN201)

---

36-36: Possible hardcoded password assigned to argument: "password"

(S106)

---

44-44: Missing return type annotation for public function `test_vote_creation`

Add return type annotation: `None`

(ANN201)

---

45-45: Possible hardcoded password assigned to argument: "password"

(S106)

---

53-53: Missing return type annotation for public function `test_total_votes`

Add return type annotation: `None`

(ANN201)

---

54-54: Possible hardcoded password assigned to argument: "password"

(S106)

---

55-55: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

---

`33-34`: _🧹 Nitpick (assertive)_

**STRING REPRESENTATION VERIFICATION MISSING IN COMMENT TEST.**

TEST ASSERTS USER, POST AND CONTENT BUT DOES NOT VERIFY THAT `str(comment)` MATCHES EXPECTED REPRESENTATION BASED ON MODEL DEFINITION.

```diff
        assert comment.user == user
        assert comment.post == post
        assert comment.content == "Nice post!"
-        assert str(comment) == f"Comment by {user} on {post}"
+        expected_str = f"Comment by {user} on {post}"
+        assert str(comment) == expected_str
```



> Likely an incorrect or invalid review comment.

</details>
<details>
<summary>server/backend/users/tests/test_serializers_auth.py (1)</summary>

`8-85`: **SERIALIZER TEST SUITE IS WELL-STRUCTURED AND COMPREHENSIVE.**

TESTS EFFECTIVELY VERIFY:
- USER REGISTRATION FOR BOTH STUDENT AND INSTRUCTOR ROLES
- VALIDATION OF INVALID ROLES
- LOGIN WITH VALID CREDENTIALS
- REJECTION OF INVALID CREDENTIALS

TESTS ARE LOGICAL AND COVER BOTH POSITIVE AND NEGATIVE TEST CASES.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

11-11: Missing return type annotation for public function `test_register_serializer`

Add return type annotation: `None`

(ANN201)

---

31-31: Missing return type annotation for public function `test_register_serializer_with_instructor`

Add return type annotation: `None`

(ANN201)

---

51-51: Missing return type annotation for public function `test_register_serializer_invalid_role`

Add return type annotation: `None`

(ANN201)

---

63-63: Missing return type annotation for public function `test_login_serializer_valid`

Add return type annotation: `None`

(ANN201)

---

64-64: Local variable `user` is assigned to but never used

Remove assignment to unused variable `user`

(F841)

---

64-64: Possible hardcoded password assigned to argument: "password"

(S106)

---

77-77: Missing return type annotation for public function `test_login_serializer_invalid_credentials`

Add return type annotation: `None`

(ANN201)

</details>

</details>

</details>
<details>
<summary>server/backend/quiz/migrations/0001_initial.py (4)</summary>

`1-6`: **MIGRATION FILE CREATED. INITIAL SCHEMA DETECTED.**

FILE CONTAINS PROPER IMPORTS FOR DJANGO MIGRATIONS MODULE. ALL NECESSARY DEPENDENCIES IMPORTED CORRECTLY.

---

`8-14`: **MIGRATION CLASS CONFIGURATION VALIDATED.**

INITIAL MIGRATION CORRECTLY CONFIGURED WITH USER MODEL DEPENDENCY. THIS IS CORRECT FOR QUIZ MODELS THAT REFERENCE USER.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

11-13: Mutable class attributes should be annotated with `typing.ClassVar`

(RUF012)

</details>

</details>

---

`33-54`: **QUESTION MODEL STRUCTURE VALIDATED.**

QUESTION MODEL CORRECTLY ASSOCIATES WITH CATEGORY VIA FOREIGN KEY. CASCADE DELETION WILL REMOVE QUESTIONS WHEN CATEGORY IS DELETED. THIS IS APPROPRIATE DATA INTEGRITY BEHAVIOR.


RECOMMENDATION: ADD TRAILING COMMA AFTER CASCADE REFERENCE FOR CONSISTENCY WITH STYLE CONVENTIONS.
```diff
-                        on_delete=django.db.models.deletion.CASCADE, to="quiz.category"
+                        on_delete=django.db.models.deletion.CASCADE, to="quiz.category",
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

50-50: Trailing comma missing

Add trailing comma

(COM812)

</details>

</details>

---

`55-78`: **OPTION MODEL STRUCTURE CONFIRMED FUNCTIONAL.**

OPTION MODEL CORRECTLY IMPLEMENTS RELATIONSHIP TO QUESTION WITH RELATED_NAME FOR REVERSE ACCESS. IS_CORRECT BOOLEAN FLAG PROPERLY DEFAULTED TO FALSE. MODEL STRUCTURE IS OPTIMAL.

</details>
<details>
<summary>server/backend/courses/migrations/0001_initial.py (5)</summary>

`1-6`: **MIGRATION FILE CREATED. INITIAL SCHEMA DETECTED.**

FILE CONTAINS PROPER IMPORTS FOR DJANGO MIGRATIONS MODULE. ALL NECESSARY DEPENDENCIES IMPORTED CORRECTLY.

---

`17-54`: **COURSE MODEL STRUCTURE ANALYSIS COMPLETE.**

COURSE MODEL CONTAINS APPROPRIATE FIELDS WITH PROPER CONSTRAINTS. DIFFICULTY FIELD CORRECTLY LIMITS VALUES TO PREDEFINED CHOICES. NULLABLE INSTRUCTOR REFERENCE ALLOWS COURSES WITHOUT SPECIFIC INSTRUCTOR.

---

`55-91`: **COURSECONTENTS MODEL STRUCTURE VALIDATED.**

COURSECONTENTS MODEL CORRECTLY IMPLEMENTS CONTENT TYPE OPTIONS AND RELATIONSHIP TO COURSE. URL AND TEXT_CONTENT FIELDS APPROPRIATELY HANDLE DIFFERENT CONTENT FORMATS. STRUCTURE IS LOGICAL.

---

`124-156`: **RATING MODEL STRUCTURE VALIDATED.**

RATING MODEL ALLOWS USERS TO RATE COURSES ONCE. STRUCTURE PREVENTS MULTIPLE RATINGS FROM SAME USER ON SAME COURSE. RELATIONSHIPS PROPERLY CONFIGURED.

---

`8-14`: <details>
<summary>❓ Verification inconclusive</summary>

**MIGRATION CLASS CONFIGURATION DETECTED.**

MIGRATION CONFIGURED WITH "__FIRST__" DEPENDENCY ON USERS APP. CAUTION: THIS INDICATES POTENTIAL CIRCULAR DEPENDENCY BETWEEN APPS. MONITOR FOR FUTURE MIGRATION ISSUES.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check for potential circular dependencies between apps
grep -r "dependencies = \[" server/backend/*/migrations/*.py | grep "__first__"
```

Length of output: 491

---



</details>

**ACTION REQUIRED: Verify Migration Dependency Configuration**

The migration file `server/backend/courses/migrations/0001_initial.py` contains a dependency on `("users", "__first__")`, which might be a red flag for potential circular dependencies between apps. Note that the grep command used for verification did not capture any output due to its limitation with multi-line patterns, so automatic detection is inconclusive.

- **File to Review:** `server/backend/courses/migrations/0001_initial.py`
- **Concern:** The use of `"__first__"` in the dependency configuration may lead to circular dependency issues if app migrations are not properly ordered.
- **Next Steps:** Please manually verify the migration ordering, especially between the `courses` and `users` apps, to ensure there is no risk of circular dependencies arising in future migrations.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

11-14: Mutable class attributes should be annotated with `typing.ClassVar`

(RUF012)

</details>

</details>

</details>
<details>
<summary>server/backend/users/tests/test_models.py (2)</summary>

`1-5`: **TEST CLASS IMPORTS VALIDATED.**

IMPORTS CORRECTLY REFERENCE REQUIRED MODELS FOR TESTING. USER, STUDENT, AND INSTRUCTOR MODELS PROPERLY IMPORTED.

---

`35-37`: **USER STRING REPRESENTATION TEST APPROVED.**

TEST VERIFIES USER.__STR__() METHOD RETURNS USERNAME. TEST IS CONCISE AND FUNCTIONAL.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

35-35: Missing return type annotation for public function `test_user_str_method`

Add return type annotation: `None`

(ANN201)

---

36-36: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

</details>
<details>
<summary>server/backend/quiz/tests/test_models.py (5)</summary>

`7-14`: **CATEGORY MODEL TEST VALIDATED.**

TEST CORRECTLY VERIFIES CATEGORY CREATION AND ATTRIBUTE ASSIGNMENT. TEST LOGIC IS SOUND.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

10-10: Missing return type annotation for public function `test_category_model`

Add return type annotation: `None`

(ANN201)

</details>

</details>

---

`15-20`: **QUESTION MODEL TEST VALIDATED.**

TEST CORRECTLY VERIFIES QUESTION CREATION WITH PROPER CATEGORY RELATIONSHIP. TEST LOGIC IS SOUND.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

15-15: Missing return type annotation for public function `test_question_model`

Add return type annotation: `None`

(ANN201)

</details>

</details>

---

`21-27`: **OPTION MODEL TEST VALIDATED.**

TEST CORRECTLY VERIFIES OPTION CREATION WITH PROPER QUESTION RELATIONSHIP AND IS_CORRECT ATTRIBUTE. TEST LOGIC IS SOUND.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

21-21: Missing return type annotation for public function `test_option_model`

Add return type annotation: `None`

(ANN201)

</details>

</details>

---

`28-37`: **QUIZ ATTEMPT MODEL TEST VALIDATED.**

TEST CORRECTLY VERIFIES QUIZATTEMPT CREATION WITH PROPER USER AND CATEGORY RELATIONSHIPS. TEST CONTAINS HARDCODED PASSWORD - ACCEPTABLE IN TEST CONTEXT.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

28-28: Missing return type annotation for public function `test_quiz_attempt_model`

Add return type annotation: `None`

(ANN201)

---

30-30: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

---

`38-49`: **USER ANSWER MODEL TEST VALIDATED.**

TEST CORRECTLY VERIFIES USERANSWER CREATION WITH PROPER RELATIONSHIPS. TEST LOGIC IS COMPREHENSIVE AND VERIFIES ALL RELEVANT ATTRIBUTES.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

38-38: Missing return type annotation for public function `test_user_answer_model`

Add return type annotation: `None`

(ANN201)

---

40-40: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

</details>
<details>
<summary>server/backend/users/tests/test_serializers_token_dashboard.py (1)</summary>

`1-5`: **IMPORTS DETECTED. PROCESSING.**

IMPORT ANALYSIS COMPLETE. NECESSARY MODULES IMPORTED FOR TESTING TOKEN SERIALIZERS AND DASHBOARD SERIALIZER.

</details>
<details>
<summary>server/backend/users/tests/test_serializers_user.py (1)</summary>

`1-5`: **IMPORTS DETECTED. PROCESSING.**

IMPORT ANALYSIS COMPLETE. NECESSARY MODULES IMPORTED FOR TESTING USER SERIALIZERS.

</details>
<details>
<summary>server/backend/users/tests/test_views.py (1)</summary>

`1-6`: **IMPORTS DETECTED. PROCESSING.**

IMPORT ANALYSIS COMPLETE. NECESSARY MODULES IMPORTED FOR TESTING API VIEWS.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-12T16:58:09Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>server/backend/users/tests/test_views.py (1)</summary><blockquote>

`1-95`: _🧹 Nitpick (assertive)_

**PASSWORD HANDLING IN TESTS RECOMMENDATION.**

MULTIPLE HARDCODED PASSWORDS DETECTED IN TEST METHODS. WHILE ACCEPTABLE IN TEST ENVIRONMENT, CONSIDER DEFINING CONSTANT FOR CONSISTENCY.

```diff
+import pytest
+from rest_framework.test import APIClient
+from rest_framework_simplejwt.tokens import RefreshToken
+from rest_framework import status
+from users.models import User
+
+# CONSTANT FOR TEST PASSWORDS
+TEST_PASSWORD = 'password123'
+
+@pytest.mark.django_db
+class TestAuthViews:
     client = APIClient()
     def test_register_view(self):
         data = {
             'username': 'testuser',
             'email': 'testuser@example.com',
-            'password': 'password123',
+            'password': TEST_PASSWORD,
             'role': 'student',
         }
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

5-5: `users.models.Instructor` imported but unused

Remove unused import: `users.models.Instructor`

(F401)

---

10-10: Missing return type annotation for public function `test_register_view`

Add return type annotation: `None`

(ANN201)

---

25-25: Missing return type annotation for public function `test_login_view`

Add return type annotation: `None`

(ANN201)

---

30-30: Local variable `user` is assigned to but never used

Remove assignment to unused variable `user`

(F841)

---

30-30: Possible hardcoded password assigned to argument: "password"

(S106)

---

39-39: Missing return type annotation for public function `test_logout_view`

Add return type annotation: `None`

(ANN201)

---

40-40: Possible hardcoded password assigned to argument: "password"

(S106)

---

49-49: Missing return type annotation for public function `test_logout_view_no_token`

Add return type annotation: `None`

(ANN201)

---

55-55: Missing return type annotation for public function `test_dashboard_stats_view`

Add return type annotation: `None`

(ANN201)

---

56-56: Possible hardcoded password assigned to argument: "password"

(S106)

---

68-68: Missing return type annotation for public function `test_dashboard_stats_view_no_permission`

Add return type annotation: `None`

(ANN201)

---

69-69: Possible hardcoded password assigned to argument: "password"

(S106)

---

82-82: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>♻️ Duplicate comments (6)</summary><blockquote>

<details>
<summary>server/backend/users/tests/test_views.py (6)</summary><blockquote>

`10-24`: _🧹 Nitpick (assertive)_

**REGISTRATION TEST IMPLEMENTATION APPROVED. TYPE ANNOTATION MISSING.**

TEST SUCCESSFULLY VALIDATES USER REGISTRATION PROCESS AND RESPONSE STRUCTURE. MISSING RETURN TYPE ANNOTATION DETECTED.


```diff
-    def test_register_view(self):
+    def test_register_view(self) -> None:
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

10-10: Missing return type annotation for public function `test_register_view`

Add return type annotation: `None`

(ANN201)

</details>

</details>

---

`25-38`: _🧹 Nitpick (assertive)_

**LOGIN TEST IMPLEMENTATION APPROVED. UNUSED VARIABLE WARNING.**

TEST CORRECTLY VALIDATES LOGIN FUNCTIONALITY. VARIABLE 'user' CREATED BUT REFERENCE NOT UTILIZED IN ASSERTIONS.


```diff
-    def test_login_view(self):
+    def test_login_view(self) -> None:
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

25-25: Missing return type annotation for public function `test_login_view`

Add return type annotation: `None`

(ANN201)

---

30-30: Local variable `user` is assigned to but never used

Remove assignment to unused variable `user`

(F841)

---

30-30: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

---

`39-48`: _🧹 Nitpick (assertive)_

**LOGOUT TEST IMPLEMENTATION APPROVED. TYPE ANNOTATION MISSING.**

TEST CORRECTLY VALIDATES LOGOUT ENDPOINT WITH VALID TOKEN. MISSING RETURN TYPE ANNOTATION DETECTED.


```diff
-    def test_logout_view(self):
+    def test_logout_view(self) -> None:
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

39-39: Missing return type annotation for public function `test_logout_view`

Add return type annotation: `None`

(ANN201)

---

40-40: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

---

`49-54`: _🧹 Nitpick (assertive)_

**LOGOUT ERROR TEST IMPLEMENTATION APPROVED. TYPE ANNOTATION MISSING.**

TEST CORRECTLY VALIDATES ERROR HANDLING FOR LOGOUT WITHOUT TOKEN. MISSING RETURN TYPE ANNOTATION DETECTED.


```diff
-    def test_logout_view_no_token(self):
+    def test_logout_view_no_token(self) -> None:
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

49-49: Missing return type annotation for public function `test_logout_view_no_token`

Add return type annotation: `None`

(ANN201)

</details>

</details>

---

`55-67`: _🧹 Nitpick (assertive)_

**DASHBOARD STATS TEST IMPLEMENTATION APPROVED. TYPE ANNOTATION MISSING.**

TEST CORRECTLY VALIDATES ADMIN ACCESS TO DASHBOARD STATISTICS. MISSING RETURN TYPE ANNOTATION DETECTED.


```diff
-    def test_dashboard_stats_view(self):
+    def test_dashboard_stats_view(self) -> None:
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

55-55: Missing return type annotation for public function `test_dashboard_stats_view`

Add return type annotation: `None`

(ANN201)

---

56-56: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

---

`68-76`: _🧹 Nitpick (assertive)_

**PERMISSION DENIAL TEST IMPLEMENTATION APPROVED. TYPE ANNOTATION MISSING.**

TEST CORRECTLY VALIDATES ACCESS RESTRICTION FOR NON-ADMIN USERS. MISSING RETURN TYPE ANNOTATION DETECTED.


```diff
-    def test_dashboard_stats_view_no_permission(self):
+    def test_dashboard_stats_view_no_permission(self) -> None:
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

68-68: Missing return type annotation for public function `test_dashboard_stats_view_no_permission`

Add return type annotation: `None`

(ANN201)

---

69-69: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between ab23cdec39243b7a31704dbbd85442ac8260115c and d10ce12345d38035b28f00ba18bf1e235f45cc0d.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `server/backend/users/tests/test_views.py` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>server/backend/users/tests/test_views.py (1)</summary><blockquote>

<details>
<summary>server/backend/users/models.py (2)</summary>

* `Instructor` (15-21)
* `User` (4-13)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Ruff (0.8.2)</summary>

<details>
<summary>server/backend/users/tests/test_views.py</summary>

5-5: `users.models.Instructor` imported but unused

Remove unused import: `users.models.Instructor`

(F401)

---

10-10: Missing return type annotation for public function `test_register_view`

Add return type annotation: `None`

(ANN201)

---

25-25: Missing return type annotation for public function `test_login_view`

Add return type annotation: `None`

(ANN201)

---

30-30: Local variable `user` is assigned to but never used

Remove assignment to unused variable `user`

(F841)

---

30-30: Possible hardcoded password assigned to argument: "password"

(S106)

---

39-39: Missing return type annotation for public function `test_logout_view`

Add return type annotation: `None`

(ANN201)

---

40-40: Possible hardcoded password assigned to argument: "password"

(S106)

---

49-49: Missing return type annotation for public function `test_logout_view_no_token`

Add return type annotation: `None`

(ANN201)

---

55-55: Missing return type annotation for public function `test_dashboard_stats_view`

Add return type annotation: `None`

(ANN201)

---

56-56: Possible hardcoded password assigned to argument: "password"

(S106)

---

68-68: Missing return type annotation for public function `test_dashboard_stats_view_no_permission`

Add return type annotation: `None`

(ANN201)

---

69-69: Possible hardcoded password assigned to argument: "password"

(S106)

---

82-82: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>server/backend/users/tests/test_views.py (2)</summary>

`9-10`: **CLIENT INITIALIZATION APPROVED.**

OPTIMAL PLACEMENT OF CLIENT INITIALIZATION AT CLASS LEVEL. AVOIDS REPETITION IN TEST METHODS. EFFICIENCY CONFIRMED.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

10-10: Missing return type annotation for public function `test_register_view`

Add return type annotation: `None`

(ANN201)

</details>

</details>

---

`81-95`: <details>
<summary>✅ Verification successful</summary>

**INSTRUCTOR VIEWSET TEST MODIFICATIONS APPROVED.**

PREVIOUS REVIEW RECOMMENDATIONS IMPLEMENTED. AUTHENTICATION COMMENTED OUT BUT ENDPOINT TESTING SUCCESSFUL. CONSIDER ENABLING AUTHENTICATION IF ENDPOINT REQUIRES IT.


---

🏁 Script executed:

```shell
#!/bin/bash
# Check if instructor endpoint requires authentication
rg -A 5 "class InstructorViewSet" --type py
```

Length of output: 341

---



</details>

**INSTRUCTOR VIEWSET TEST APPROVED – No Further Action Needed**

The test modifications in `server/backend/users/tests/test_views.py` are correct. Our verification confirms the `InstructorViewSet` in `server/backend/users/views.py` uses `permission_classes = [AllowAny]`, meaning authentication is not required. The commented-out authentication step is appropriate given the endpoint's current behavior.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

82-82: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-12T17:22:20Z

Review comments:
diff_hunk: @@ -5,8 +5,8 @@ sonar.organization=learnathon-by-geeky-solutions
 sonar.host.url=https://sonarcloud.io
 
 # Tell SonarCloud which folder contains your real code
-sonar.sources=frontend/,server/
-sonar.tests=server/backend
+sonar.sources=frontend/,server/backend
+sonar.tests=server/backend/courses, server/backend/users
path: sonar-project.properties
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**CONFIGURATION UPDATE DETECTED. VALIDATION REQUIRED.**

ANALYZING CONFIGURATION CHANGES:
- SOURCE PATH MODIFIED TO TARGET MORE SPECIFIC LOCATION
- TEST PATHS CONSTRAINED TO SPECIFIC MODULES

RECOMMENDATION: REMOVE SPACE AFTER COMMA IN LINE 9. STANDARD FORMAT FOR PROPERTY LISTS UTILIZES COMMA-SEPARATED-VALUES WITHOUT SPACES.


```diff
-sonar.tests=server/backend/courses, server/backend/users
+sonar.tests=server/backend/courses,server/backend/users
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
sonar.sources=frontend/,server/backend
sonar.tests=server/backend/courses,server/backend/users
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,28 @@
+# Generated by Django 5.1.8 on 2025-04-08 08:46
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("courses", "0001_initial"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="course",
+            name="image",
+            field=models.URLField(blank=True, null=True),
+        ),
+        migrations.AlterField(
+            model_name="rating",
+            name="rating",
+            field=models.PositiveSmallIntegerField(
+                validators=[
+                    django.core.validators.MinValueValidator(1),
+                    django.core.validators.MaxValueValidator(5),
+                ]
+            ),
+        ),
path: server/backend/courses/migrations/0002_course_image_alter_rating_rating.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Beep Boop...** Setting rating validators from 1 to 5 is a standard approach. This ensures valid input. Consider adding trailing commas for style in the validator list.  

```diff
-                    django.core.validators.MaxValueValidator(5)
+                    django.core.validators.MaxValueValidator(5),
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        migrations.AlterField(
            model_name="rating",
            name="rating",
            field=models.PositiveSmallIntegerField(
                validators=[
                    django.core.validators.MinValueValidator(1),
                    django.core.validators.MaxValueValidator(5),
                ]
            ),
        ),
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

25-25: Trailing comma missing

Add trailing comma

(COM812)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,17 +20,32 @@ jobs:
       - name: Set up Python
         uses: actions/setup-python@v2
         with:
-          python-version: '3.8'  # Change this to the version you are using
+          python-version: '3.10'
 
       - name: Install dependencies
         run: |
           python -m pip install --upgrade pip
-          pip install -r requirements.txt
+          pip install -r ./server/requirements.txt
           pip install pytest pytest-cov
 
+      - name: Create .env file
+        run: |
+          {
+            echo "SECRET_KEY=${{ secrets.SECRET_KEY }}"
+            echo "ALLOWED_HOSTS=${{ secrets.ALLOWED_HOSTS }}"
+            echo "DB_NAME=${{ secrets.DB_NAME }}"
+            echo "DB_USER=${{ secrets.DB_USER }}"
+            echo "DB_PASSWORD=${{ secrets.DB_PASSWORD }}"
+            echo "DB_HOST=${{ secrets.DB_HOST }}"
+            echo "DB_PORT=${{ secrets.DB_PORT }}"
+            echo "TEST_PASSWORD=${{ secrets.TEST_PASSWORD }}"
+          } > .env
path: .github/workflows/sonarqube.yml
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Beep Boop...** Creating a `.env` file is a good approach for injecting secrets. Please ensure secrets are not logged or exposed elsewhere.

---

🏁 Script executed:

```shell
#!/bin/bash
# Searching for any print statements that might inadvertently log secrets
rg 'print\('
```

Length of output: 603

---



</details>

**Action: Ensure Debug Print Statements Don't Expose Secrets**

Beep boop! The GitHub workflow’s approach to create a `.env` file for secret injection looks secure. However, our verification detected several unguarded `print()` statements that could accidentally reveal sensitive data. Please review the following:

- **server/backend/users/views.py**  
  - `print(serializer.errors)`  
  _Potential risk:_ Printing error details might expose sensitive information.
  
- **server/backend/users/serializers.py**  
  - `print("mmmmm", token)` appears twice.  
  _Potential risk:_ Directly printing tokens could leak secret values.
  
- **server/backend/users/tests/test_models.py**  
  - Multiple `print()` calls that output user IDs and database counts may be harmless in tests but should be reviewed to ensure no sensitive data is logged.

Please remove these debugging print statements or guard them to run only in non-production environments.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,16 +97,29 @@
 # Database
 # https://docs.djangoproject.com/en/5.1/ref/settings/#databases
 
-DATABASES = {
-    'default': {
-        'ENGINE': 'django.db.backends.postgresql',
-        'NAME': config('DB_NAME'),
-        'USER': config('DB_USER'),
-        'PASSWORD': config('DB_PASSWORD'),
-        'HOST': config('DB_HOST'),
-        'PORT': config('DB_PORT', cast=int),
+if os.getenv('GITHUB_WORKFLOW') or os.getenv('CI'):
+    # If running inside GitHub Actions
+    DATABASES = {
+        'default': {
+            'ENGINE': 'django.db.backends.postgresql',
+            'NAME': os.getenv('POSTGRES_DB', 'test_db'),
+            'USER': os.getenv('POSTGRES_USER', 'postgres'),
+            'PASSWORD': os.getenv('POSTGRES_PASSWORD', 'postgres'),
+            'HOST': 'localhost',
+            'PORT': '5432',
+        }
+    }
+else:
+    DATABASES = {
+        'default': {
+            'ENGINE': 'django.db.backends.postgresql',
+            'NAME': config('DB_NAME'),
+            'USER': config('DB_USER'),
+            'PASSWORD': config('DB_PASSWORD'),
+            'HOST': config('DB_HOST'),
+            'PORT': config('DB_PORT', cast=int),
+        }
path: server/backend/backend/settings.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Beep Boop...** Non-CI database config references `config(...)` calls, which is fine. Trailing comma after `'PORT': config('DB_PORT', cast=int)` is optional but can improve readability.  

```diff
'PORT': config('DB_PORT', cast=int)
-}
+},
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': config('DB_NAME'),
            'USER': config('DB_USER'),
            'PASSWORD': config('DB_PASSWORD'),
            'HOST': config('DB_HOST'),
            'PORT': config('DB_PORT', cast=int),
        },
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

121-121: Trailing comma missing

Add trailing comma

(COM812)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -96,16 +97,29 @@
 # Database
 # https://docs.djangoproject.com/en/5.1/ref/settings/#databases
 
-DATABASES = {
-    'default': {
-        'ENGINE': 'django.db.backends.postgresql',
-        'NAME': config('DB_NAME'),
-        'USER': config('DB_USER'),
-        'PASSWORD': config('DB_PASSWORD'),
-        'HOST': config('DB_HOST'),
-        'PORT': config('DB_PORT', cast=int),
+if os.getenv('GITHUB_WORKFLOW') or os.getenv('CI'):
+    # If running inside GitHub Actions
+    DATABASES = {
+        'default': {
+            'ENGINE': 'django.db.backends.postgresql',
+            'NAME': os.getenv('POSTGRES_DB', 'test_db'),
+            'USER': os.getenv('POSTGRES_USER', 'postgres'),
+            'PASSWORD': os.getenv('POSTGRES_PASSWORD', 'postgres'),
+            'HOST': 'localhost',
+            'PORT': '5432',
+        }
path: server/backend/backend/settings.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Beep Boop...** Conditional database config for CI is logical. Consider adding a trailing comma after `'HOST': 'localhost'` for style consistency.  

```diff
'HOST': 'localhost',
'PORT': '5432'
-}
+},
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
if os.getenv('GITHUB_WORKFLOW') or os.getenv('CI'):
    # If running inside GitHub Actions
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': os.getenv('POSTGRES_DB', 'test_db'),
            'USER': os.getenv('POSTGRES_USER', 'postgres'),
            'PASSWORD': os.getenv('POSTGRES_PASSWORD', 'postgres'),
            'HOST': 'localhost',
            'PORT': '5432',
        },
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

110-110: Trailing comma missing

Add trailing comma

(COM812)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit fd19657
created_at: 2025-04-12T16:57:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,187 @@
+# Generated by Django 5.1.4 on 2025-03-19 21:25
+
+import django.contrib.auth.models
+import django.contrib.auth.validators
+import django.db.models.deletion
+import django.utils.timezone
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    initial = True
+
+    dependencies = [
+        ("auth", "0012_alter_user_first_name_max_length"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="User",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("password", models.CharField(max_length=128, verbose_name="password")),
+                (
+                    "last_login",
+                    models.DateTimeField(
+                        blank=True, null=True, verbose_name="last login"
+                    ),
+                ),
+                (
+                    "is_superuser",
+                    models.BooleanField(
+                        default=False,
+                        help_text="Designates that this user has all permissions without explicitly assigning them.",
+                        verbose_name="superuser status",
+                    ),
+                ),
+                (
+                    "username",
+                    models.CharField(
+                        error_messages={
+                            "unique": "A user with that username already exists."
+                        },
+                        help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
+                        max_length=150,
+                        unique=True,
+                        validators=[
+                            django.contrib.auth.validators.UnicodeUsernameValidator()
+                        ],
+                        verbose_name="username",
+                    ),
+                ),
+                (
+                    "first_name",
+                    models.CharField(
+                        blank=True, max_length=150, verbose_name="first name"
+                    ),
+                ),
+                (
+                    "last_name",
+                    models.CharField(
+                        blank=True, max_length=150, verbose_name="last name"
+                    ),
+                ),
+                (
+                    "email",
+                    models.EmailField(
+                        blank=True, max_length=254, verbose_name="email address"
+                    ),
+                ),
+                (
+                    "is_staff",
+                    models.BooleanField(
+                        default=False,
+                        help_text="Designates whether the user can log into this admin site.",
+                        verbose_name="staff status",
+                    ),
+                ),
+                (
+                    "is_active",
+                    models.BooleanField(
+                        default=True,
+                        help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
+                        verbose_name="active",
+                    ),
+                ),
+                (
+                    "date_joined",
+                    models.DateTimeField(
+                        default=django.utils.timezone.now, verbose_name="date joined"
+                    ),
+                ),
+                (
+                    "role",
+                    models.CharField(
+                        choices=[("student", "Student"), ("instructor", "Instructor")],
+                        max_length=20,
+                    ),
+                ),
+                ("bio", models.TextField(blank=True, null=True)),
+                (
+                    "groups",
+                    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",
+                    ),
+                ),
+                (
+                    "user_permissions",
+                    models.ManyToManyField(
+                        blank=True,
+                        help_text="Specific permissions for this user.",
+                        related_name="user_set",
+                        related_query_name="user",
+                        to="auth.permission",
+                        verbose_name="user permissions",
+                    ),
+                ),
+            ],
+            options={
+                "verbose_name": "user",
+                "verbose_name_plural": "users",
+                "abstract": False,
+            },
+            managers=[
+                ("objects", django.contrib.auth.models.UserManager()),
+            ],
+        ),
path: server/backend/users/migrations/0001_initial.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Beep Boop...** `User` model extension is valid. Trimming lines is typical. Consider trailing commas in multi-line arguments for style if desired.  

```diff
error_messages={
    "unique": "A user with that username already exists."
-}
+},
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        migrations.CreateModel(
            name="User",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("password", models.CharField(max_length=128, verbose_name="password")),
                (
                    "last_login",
                    models.DateTimeField(
                        blank=True, null=True, verbose_name="last login"
                    ),
                ),
                (
                    "is_superuser",
                    models.BooleanField(
                        default=False,
                        help_text="Designates that this user has all permissions without explicitly assigning them.",
                        verbose_name="superuser status",
                    ),
                ),
                (
                    "username",
                    models.CharField(
                        error_messages={
                            "unique": "A user with that username already exists."
                        },
                        help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
                        max_length=150,
                        unique=True,
                        validators=[
                            django.contrib.auth.validators.UnicodeUsernameValidator()
                        ],
                        verbose_name="username",
                    ),
                ),
                (
                    "first_name",
                    models.CharField(
                        blank=True, max_length=150, verbose_name="first name"
                    ),
                ),
                (
                    "last_name",
                    models.CharField(
                        blank=True, max_length=150, verbose_name="last name"
                    ),
                ),
                (
                    "email",
                    models.EmailField(
                        blank=True, max_length=254, verbose_name="email address"
                    ),
                ),
                (
                    "is_staff",
                    models.BooleanField(
                        default=False,
                        help_text="Designates whether the user can log into this admin site.",
                        verbose_name="staff status",
                    ),
                ),
                (
                    "is_active",
                    models.BooleanField(
                        default=True,
                        help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
                        verbose_name="active",
                    ),
                ),
                (
                    "date_joined",
                    models.DateTimeField(
                        default=django.utils.timezone.now, verbose_name="date joined"
                    ),
                ),
                (
                    "role",
                    models.CharField(
                        choices=[("student", "Student"), ("instructor", "Instructor")],
                        max_length=20,
                    ),
                ),
                ("bio", models.TextField(blank=True, null=True)),
                (
                    "groups",
                    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",
                    ),
                ),
                (
                    "user_permissions",
                    models.ManyToManyField(
                        blank=True,
                        help_text="Specific permissions for this user.",
                        related_name="user_set",
                        related_query_name="user",
                        to="auth.permission",
                        verbose_name="user permissions",
                    ),
                ),
            ],
            options={
                "verbose_name": "user",
                "verbose_name_plural": "users",
                "abstract": False,
            },
            managers=[
                ("objects", django.contrib.auth.models.UserManager()),
            ],
        ),
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

35-35: Trailing comma missing

Add trailing comma

(COM812)

---

50-50: Trailing comma missing

Add trailing comma

(COM812)

---

56-56: Trailing comma missing

Add trailing comma

(COM812)

---

64-64: Trailing comma missing

Add trailing comma

(COM812)

---

70-70: Trailing comma missing

Add trailing comma

(COM812)

---

76-76: Trailing comma missing

Add trailing comma

(COM812)

---

98-98: Trailing comma missing

Add trailing comma

(COM812)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,81 @@
+import pytest
+from rest_framework.test import APIClient
+from rest_framework import status
+from users.models import User
+from quiz.models import Category, Question, Option, QuizAttempt, UserAnswer
+
+@pytest.mark.django_db
+class TestQuizViews:
+
+    client = APIClient()
+
+    def test_category_view_set(self):
+        category = Category.objects.create(name="Math", description="Math questions")
+        response = self.client.get(f'/quiz/')
+        assert response.status_code == status.HTTP_200_OK
path: server/backend/quiz/tests/test_views.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**UNNECESSARY F-STRING DETECTED.**

LINE CONTAINS F-STRING WITHOUT ANY VARIABLE PLACEHOLDERS. F-PREFIX IS REDUNDANT.

```diff
-        response = self.client.get(f'/quiz/')
+        response = self.client.get('/quiz/')
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        response = self.client.get('/quiz/')
        assert response.status_code == status.HTTP_200_OK
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

14-14: f-string without any placeholders

Remove extraneous `f` prefix

(F541)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,81 @@
+import pytest
+from rest_framework.test import APIClient
+from rest_framework import status
+from users.models import User
+from quiz.models import Category, Question, Option, QuizAttempt, UserAnswer
+
path: server/backend/quiz/tests/test_views.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**DETECTING UNUSED IMPORTS. OPTIMIZATION RECOMMENDED.**

IMPORTS `QuizAttempt` AND `UserAnswer` ARE IMPORTED BUT NEVER UTILIZED IN TEST CODE.

```diff
-from quiz.models import Category, Question, Option, QuizAttempt, UserAnswer
+from quiz.models import Category, Question, Option
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import pytest
from rest_framework.test import APIClient
from rest_framework import status
from users.models import User
from quiz.models import Category, Question, Option
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

5-5: `quiz.models.QuizAttempt` imported but unused

Remove unused import

(F401)

---

5-5: `quiz.models.UserAnswer` imported but unused

Remove unused import

(F401)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,81 @@
+import pytest
+from rest_framework.test import APIClient
+from rest_framework import status
+from users.models import User
+from quiz.models import Category, Question, Option, QuizAttempt, UserAnswer
+
+@pytest.mark.django_db
+class TestQuizViews:
+
+    client = APIClient()
+
+    def test_category_view_set(self):
+        category = Category.objects.create(name="Math", description="Math questions")
+        response = self.client.get(f'/quiz/')
+        assert response.status_code == status.HTTP_200_OK
+
+    def test_quiz_api_view(self):
+        category = Category.objects.create(name="Math", description="Math questions")
+        question1 = Question.objects.create(category=category, text="What is 2+2?")
+        question2 = Question.objects.create(category=category, text="What is 3+3?")
+        Option.objects.create(question=question1, text="4", is_correct=True)
+        Option.objects.create(question=question2, text="6", is_correct=True)
+
+        response = self.client.get(f'/quiz/{category.id}/')
+        assert response.status_code == status.HTTP_200_OK
+        assert len(response.data['questions']) == 2
+
+    def test_add_quiz_view(self):
+        user = User.objects.create_superuser(username="testuser", password="password123")
+        category_data = {"name": "Math", "description": "Math related questions"}
+        self.client.force_authenticate(user=user)
+        response = self.client.post('/quiz/add/', category_data, format='json')
+        assert response.status_code == status.HTTP_201_CREATED
+        assert response.data['name'] == "Math"
+
+    def test_add_question_view(self):
+        user = User.objects.create_superuser(username="testuser", password="password123")
+        category = Category.objects.create(name="Math", description="Math related questions")
+        payload = {
+            "category": category.id,
+            "text": "What is the capital of France?",
+            "options": [
+                {"text": "Paris", "is_correct": True},
+                {"text": "London", "is_correct": False},
+                {"text": "Berlin", "is_correct": False},
+                {"text": "Rome", "is_correct": False}
+            ]
+        }
path: server/backend/quiz/tests/test_views.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**MISSING TRAILING COMMAS IN DATA STRUCTURE.**

ADDING TRAILING COMMAS IN MULTI-LINE DATA STRUCTURES IMPROVES CODE MAINTAINABILITY AND REDUCES DIFF NOISE WHEN ADDITIONAL ITEMS ARE ADDED.

```diff
            "options": [
                {"text": "Paris", "is_correct": True},
                {"text": "London", "is_correct": False},
                {"text": "Berlin", "is_correct": False},
-                {"text": "Rome", "is_correct": False}
+                {"text": "Rome", "is_correct": False},
            ]
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        category = Category.objects.create(name="Math", description="Math related questions")
        payload = {
            "category": category.id,
            "text": "What is the capital of France?",
            "options": [
                {"text": "Paris", "is_correct": True},
                {"text": "London", "is_correct": False},
                {"text": "Berlin", "is_correct": False},
                {"text": "Rome", "is_correct": False},
            ]
        }
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

46-46: Trailing comma missing

Add trailing comma

(COM812)

---

47-47: Trailing comma missing

Add trailing comma

(COM812)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,84 @@
+import pytest
+from rest_framework.exceptions import ValidationError
+from rest_framework.test import APIClient
+from users.models import User, Instructor, Student
+from users.serializers import RegisterSerializer, LoginSerializer
+from django.contrib.auth import get_user_model
+
+@pytest.mark.django_db
+class TestUserSerializers:
+
+    def test_register_serializer(self):
+        user_data = {
+            'username': 'testuser',
+            'email': 'testuser@example.com',
+            'password': 'testpass123',
+            'role': 'student',
+            'bio': 'This is a test bio.',
+        }
+        serializer = RegisterSerializer(data=user_data)
+        assert serializer.is_valid()
+        user = serializer.save()
+        assert user.username == user_data['username']
+        assert user.email == user_data['email']
+        assert user.role == user_data['role']
+        assert user.bio == user_data['bio']
+        assert user.check_password(user_data['password'])
+        student = Student.objects.get(user=user)
+        assert student.course_completed == 0
+        assert student.course_enrolled == 0
+
+    def test_register_serializer_with_instructor(self):
+        user_data = {
+            'username': 'instructor1',
+            'email': 'instructor1@example.com',
+            'password': 'testpass123',
+            'role': 'instructor',
+            'bio': 'This is an instructor bio.',
+        }
+        serializer = RegisterSerializer(data=user_data)
+        assert serializer.is_valid()
+        user = serializer.save()
+        assert user.username == user_data['username']
+        assert user.email == user_data['email']
+        assert user.role == user_data['role']
+        assert user.bio == user_data['bio']
+        assert user.check_password(user_data['password'])
+        instructor = Instructor.objects.get(user=user)
+        assert instructor.designation == ''
+        assert instructor.university == ''
path: server/backend/users/tests/test_serializers_auth.py
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**POTENTIAL BUG: UNEXPECTED INSTRUCTOR DEFAULT VALUES.**

TEST EXPECTS EMPTY STRINGS FOR `designation` AND `university` FIELDS BUT MODEL MIGHT NOT GUARANTEE THIS BEHAVIOR. VERIFY MODEL DEFINITION OR ADJUST TEST EXPECTATIONS.

---

🏁 Script executed:

```shell
#!/bin/bash
# Verify how Instructor model fields are defined
rg -A 3 "class Instructor" server/backend/users/models.py
```

Length of output: 256

---



</details>

**ATTENTION: MISMATCH BETWEEN EXPECTED AND ACTUAL DEFAULT VALUES**

Hello Developer,  
Our analysis confirms that the `Instructor` model (located at `server/backend/users/models.py`) defines the `designation` and `university` fields without any default values. However, the test in `server/backend/users/tests/test_serializers_auth.py` (lines 48-49) expects these fields to default to empty strings. Please review and decide whether to:

- Update the `Instructor` model to include default values (e.g., `default=''`), or  
- Adjust the test expectations to match the current model behavior.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,84 @@
+import pytest
+from rest_framework.exceptions import ValidationError
+from rest_framework.test import APIClient
+from users.models import User, Instructor, Student
+from users.serializers import RegisterSerializer, LoginSerializer
+from django.contrib.auth import get_user_model
+
+@pytest.mark.django_db
+class TestUserSerializers:
+
+    def test_register_serializer(self):
+        user_data = {
+            'username': 'testuser',
+            'email': 'testuser@example.com',
+            'password': 'testpass123',
+            'role': 'student',
+            'bio': 'This is a test bio.',
+        }
+        serializer = RegisterSerializer(data=user_data)
+        assert serializer.is_valid()
+        user = serializer.save()
+        assert user.username == user_data['username']
+        assert user.email == user_data['email']
+        assert user.role == user_data['role']
+        assert user.bio == user_data['bio']
+        assert user.check_password(user_data['password'])
+        student = Student.objects.get(user=user)
+        assert student.course_completed == 0
+        assert student.course_enrolled == 0
+
+    def test_register_serializer_with_instructor(self):
+        user_data = {
+            'username': 'instructor1',
+            'email': 'instructor1@example.com',
+            'password': 'testpass123',
+            'role': 'instructor',
+            'bio': 'This is an instructor bio.',
+        }
+        serializer = RegisterSerializer(data=user_data)
+        assert serializer.is_valid()
+        user = serializer.save()
+        assert user.username == user_data['username']
+        assert user.email == user_data['email']
+        assert user.role == user_data['role']
+        assert user.bio == user_data['bio']
+        assert user.check_password(user_data['password'])
+        instructor = Instructor.objects.get(user=user)
+        assert instructor.designation == ''
+        assert instructor.university == ''
+
+    def test_register_serializer_invalid_role(self):
+        user_data = {
+            'username': 'invalidroleuser',
+            'email': 'invalidroleuser@example.com',
+            'password': 'testpass123',
+            'role': 'admin',
+            'bio': 'This is an invalid role bio.',
+        }
+        serializer = RegisterSerializer(data=user_data)
+        assert not serializer.is_valid()
+        assert 'role' in serializer.errors
+
+    def test_login_serializer_valid(self):
+        user = User.objects.create_user(username='testuser', password='testpass123', role='student', email='testuser@example.com')
+        login_data = {
+            'username': 'testuser',
+            'password': 'testpass123',
+        }
+        serializer = LoginSerializer(data=login_data)
+        assert serializer.is_valid()
+        response_data = serializer.validated_data
+        assert 'refresh' in response_data
+        assert 'access' in response_data
+        assert 'user' in response_data
+        assert response_data['user']['role'] == 'student'
+
path: server/backend/users/tests/test_serializers_auth.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**TEST VERIFIES LOGIN SERIALIZER CORRECTLY BUT VARIABLE ASSIGNED AND UNUSED.**

ON LINE 64, `user` VARIABLE IS CREATED BUT NOT REFERENCED AGAIN IN TEST. WHILE FUNCTIONALLY CORRECT, THIS IS UNNECESSARY ASSIGNMENT.

```diff
    def test_login_serializer_valid(self):
-        user = User.objects.create_user(username='testuser', password='testpass123', role='student', email='testuser@example.com')
+        User.objects.create_user(username='testuser', password='testpass123', role='student', email='testuser@example.com')
        login_data = {
            'username': 'testuser',
            'password': 'testpass123',
        }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    def test_login_serializer_valid(self):
        User.objects.create_user(username='testuser', password='testpass123', role='student', email='testuser@example.com')
        login_data = {
            'username': 'testuser',
            'password': 'testpass123',
        }
        serializer = LoginSerializer(data=login_data)
        assert serializer.is_valid()
        response_data = serializer.validated_data
        assert 'refresh' in response_data
        assert 'access' in response_data
        assert 'user' in response_data
        assert response_data['user']['role'] == 'student'
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

63-63: Missing return type annotation for public function `test_login_serializer_valid`

Add return type annotation: `None`

(ANN201)

---

64-64: Local variable `user` is assigned to but never used

Remove assignment to unused variable `user`

(F841)

---

64-64: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,84 @@
+import pytest
+from rest_framework.exceptions import ValidationError
+from rest_framework.test import APIClient
+from users.models import User, Instructor, Student
+from users.serializers import RegisterSerializer, LoginSerializer
+from django.contrib.auth import get_user_model
path: server/backend/users/tests/test_serializers_auth.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**UNUSED IMPORTS DETECTED.**

MODULES `APIClient` AND `get_user_model` IMPORTED BUT NOT UTILIZED IN TEST FUNCTIONS.

```diff
import pytest
from rest_framework.exceptions import ValidationError
-from rest_framework.test import APIClient
from users.models import User, Instructor, Student
from users.serializers import RegisterSerializer, LoginSerializer
-from django.contrib.auth import get_user_model
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import pytest
from rest_framework.exceptions import ValidationError
from users.models import User, Instructor, Student
from users.serializers import RegisterSerializer, LoginSerializer
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

3-3: `rest_framework.test.APIClient` imported but unused

Remove unused import: `rest_framework.test.APIClient`

(F401)

---

6-6: `django.contrib.auth.get_user_model` imported but unused

Remove unused import: `django.contrib.auth.get_user_model`

(F401)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,148 @@
+# Generated by Django 5.1.4 on 2025-04-05 17:47
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="Category",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("name", models.CharField(max_length=100)),
+                ("description", models.TextField()),
+                ("created_at", models.DateTimeField(auto_now_add=True)),
+            ],
+        ),
path: server/backend/quiz/migrations/0001_initial.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**CATEGORY MODEL STRUCTURE ANALYSIS COMPLETE.**

MODEL STRUCTURE FOR CATEGORY IS LOGICALLY SOUND. CONTAINS ESSENTIAL FIELDS (ID, NAME, DESCRIPTION, CREATED_AT). CONSIDER ADDING UNIQUE CONSTRAINT TO NAME FIELD IF DUPLICATE CATEGORIES ARE UNDESIRABLE.

```diff
-                ("name", models.CharField(max_length=100)),
+                ("name", models.CharField(max_length=100, unique=True)),
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        migrations.CreateModel(
            name="Category",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("name", models.CharField(max_length=100, unique=True)),
                ("description", models.TextField()),
                ("created_at", models.DateTimeField(auto_now_add=True)),
            ],
        ),
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,148 @@
+# Generated by Django 5.1.4 on 2025-04-05 17:47
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="Category",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("name", models.CharField(max_length=100)),
+                ("description", models.TextField()),
+                ("created_at", models.DateTimeField(auto_now_add=True)),
+            ],
+        ),
+        migrations.CreateModel(
+            name="Question",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("text", models.TextField()),
+                ("created_at", models.DateTimeField(auto_now_add=True)),
+                (
+                    "category",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE, to="quiz.category"
+                    ),
+                ),
+            ],
+        ),
+        migrations.CreateModel(
+            name="Option",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("text", models.CharField(max_length=255)),
+                ("is_correct", models.BooleanField(default=False)),
+                (
+                    "question",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="options",
+                        to="quiz.question",
+                    ),
+                ),
+            ],
+        ),
+        migrations.CreateModel(
+            name="QuizAttempt",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("score", models.IntegerField()),
+                ("completed", models.BooleanField(default=False)),
+                ("started_at", models.DateTimeField(auto_now_add=True)),
+                ("completed_at", models.DateTimeField(blank=True, null=True)),
+                (
+                    "category",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE, to="quiz.category"
+                    ),
+                ),
+                (
+                    "user",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to=settings.AUTH_USER_MODEL,
+                    ),
+                ),
+            ],
+        ),
+        migrations.CreateModel(
+            name="UserAnswer",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("is_correct", models.BooleanField(default=False)),
+                (
+                    "attempt",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="answers",
+                        to="quiz.quizattempt",
+                    ),
+                ),
+                (
+                    "question",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE, to="quiz.question"
+                    ),
+                ),
+                (
+                    "selected_option",
+                    models.ForeignKey(
+                        blank=True,
+                        null=True,
+                        on_delete=django.db.models.deletion.SET_NULL,
+                        to="quiz.option",
+                    ),
+                ),
+            ],
+        ),
path: server/backend/quiz/migrations/0001_initial.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**USERANSWER MODEL STRUCTURE VALIDATED.**

USERANSWER MODEL CORRECTLY IMPLEMENTS RELATIONSHIPS BETWEEN ATTEMPT, QUESTION AND OPTION. NULL CONSTRAINT FOR SELECTED_OPTION ALLOWS FOR UNANSWERED QUESTIONS. RELATIONSHIP STRUCTURE IS OPTIMAL.


```diff
-                        on_delete=django.db.models.deletion.CASCADE, to="quiz.question"
+                        on_delete=django.db.models.deletion.CASCADE, to="quiz.question",
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        migrations.CreateModel(
            name="UserAnswer",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("is_correct", models.BooleanField(default=False)),
                (
                    "attempt",
                    models.ForeignKey(
                        on_delete=django.db.models.deletion.CASCADE,
                        related_name="answers",
                        to="quiz.quizattempt",
                    ),
                ),
                (
                    "question",
                    models.ForeignKey(
                        on_delete=django.db.models.deletion.CASCADE, to="quiz.question",
                    ),
                ),
                (
                    "selected_option",
                    models.ForeignKey(
                        blank=True,
                        null=True,
                        on_delete=django.db.models.deletion.SET_NULL,
                        to="quiz.option",
                    ),
                ),
            ],
        ),
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

134-134: Trailing comma missing

Add trailing comma

(COM812)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,148 @@
+# Generated by Django 5.1.4 on 2025-04-05 17:47
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
path: server/backend/quiz/migrations/0001_initial.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**UNNECESSARY BLANK LINE DETECTED.**

BLANK LINE AT POSITION 7 IS REDUNDANT. RECOMMEND REMOVAL FOR OPTIMAL CODE EFFICIENCY.

```diff
-
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion

`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,148 @@
+# Generated by Django 5.1.4 on 2025-04-05 17:47
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="Category",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("name", models.CharField(max_length=100)),
+                ("description", models.TextField()),
+                ("created_at", models.DateTimeField(auto_now_add=True)),
+            ],
+        ),
+        migrations.CreateModel(
+            name="Question",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("text", models.TextField()),
+                ("created_at", models.DateTimeField(auto_now_add=True)),
+                (
+                    "category",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE, to="quiz.category"
+                    ),
+                ),
+            ],
+        ),
+        migrations.CreateModel(
+            name="Option",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("text", models.CharField(max_length=255)),
+                ("is_correct", models.BooleanField(default=False)),
+                (
+                    "question",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="options",
+                        to="quiz.question",
+                    ),
+                ),
+            ],
+        ),
+        migrations.CreateModel(
+            name="QuizAttempt",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("score", models.IntegerField()),
+                ("completed", models.BooleanField(default=False)),
+                ("started_at", models.DateTimeField(auto_now_add=True)),
+                ("completed_at", models.DateTimeField(blank=True, null=True)),
+                (
+                    "category",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE, to="quiz.category"
+                    ),
+                ),
+                (
+                    "user",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to=settings.AUTH_USER_MODEL,
+                    ),
+                ),
+            ],
+        ),
path: server/backend/quiz/migrations/0001_initial.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**QUIZATTEMPT MODEL ANALYSIS COMPLETE.**

QUIZATTEMPT MODEL CORRECTLY TRACKS USER QUIZ INTERACTION DATA. SCORE FIELD LACKS DEFAULT VALUE - THIS REQUIRES EXPLICIT VALUE ASSIGNMENT DURING CREATION. TRAILING COMMA MISSING AFTER CASCADE REFERENCE.


```diff
-                        on_delete=django.db.models.deletion.CASCADE, to="quiz.category"
+                        on_delete=django.db.models.deletion.CASCADE, to="quiz.category",
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        migrations.CreateModel(
            name="QuizAttempt",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("score", models.IntegerField()),
                ("completed", models.BooleanField(default=False)),
                ("started_at", models.DateTimeField(auto_now_add=True)),
                ("completed_at", models.DateTimeField(blank=True, null=True)),
                (
                    "category",
                    models.ForeignKey(
                        on_delete=django.db.models.deletion.CASCADE, to="quiz.category",
                    ),
                ),
                (
                    "user",
                    models.ForeignKey(
                        on_delete=django.db.models.deletion.CASCADE,
                        to=settings.AUTH_USER_MODEL,
                    ),
                ),
            ],
        ),
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

98-98: Trailing comma missing

Add trailing comma

(COM812)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,157 @@
+# Generated by Django 5.1.4 on 2025-03-19 21:21
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    initial = True
+
+    dependencies = [
+        ("users", "__first__"),
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="Course",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("title", models.CharField(max_length=255)),
+                ("description", models.TextField()),
+                ("duration", models.PositiveIntegerField()),
+                (
+                    "difficulty",
+                    models.CharField(
+                        choices=[
+                            ("beginner", "Beginner"),
+                            ("intermediate", "Intermediate"),
+                            ("advanced", "Advanced"),
+                        ],
+                        max_length=20,
+                    ),
+                ),
+                ("subject", models.CharField(max_length=255)),
+                ("ratings", models.FloatField(default=0.0)),
+                (
+                    "created_by",
+                    models.ForeignKey(
+                        null=True,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to="users.instructor",
+                    ),
+                ),
+            ],
+        ),
+        migrations.CreateModel(
+            name="CourseContents",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "content_type",
+                    models.CharField(
+                        choices=[
+                            ("video", "Video"),
+                            ("pdf", "PDF"),
+                            ("text", "Text"),
+                            ("article", "Article"),
+                        ],
+                        max_length=20,
+                    ),
+                ),
+                ("title", models.CharField(max_length=255)),
+                ("url", models.URLField(blank=True, null=True)),
+                ("text_content", models.TextField(blank=True)),
+                (
+                    "course",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="contents",
+                        to="courses.course",
+                    ),
+                ),
+            ],
+        ),
+        migrations.CreateModel(
+            name="Enrollment",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("enrolled_at", models.DateTimeField(auto_now_add=True)),
+                (
+                    "course",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE, to="courses.course"
+                    ),
+                ),
+                (
+                    "student",
+                    models.ForeignKey(
+                        null=True,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to="users.student",
+                    ),
+                ),
+            ],
+            options={
+                "unique_together": {("course", "student")},
+            },
+        ),
path: server/backend/courses/migrations/0001_initial.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**ENROLLMENT MODEL CONFIGURATION CONFIRMED.**

ENROLLMENT MODEL CORRECTLY TRACKS STUDENT COURSE REGISTRATIONS WITH TIMESTAMP. UNIQUE_TOGETHER CONSTRAINT PREVENTS DUPLICATE ENROLLMENTS. TRAILING COMMA MISSING AFTER CASCADE REFERENCE.


```diff
-                        on_delete=django.db.models.deletion.CASCADE, to="courses.course"
+                        on_delete=django.db.models.deletion.CASCADE, to="courses.course",
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        migrations.CreateModel(
            name="Enrollment",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("enrolled_at", models.DateTimeField(auto_now_add=True)),
                (
                    "course",
                    models.ForeignKey(
                        on_delete=django.db.models.deletion.CASCADE, to="courses.course",
                    ),
                ),
                (
                    "student",
                    models.ForeignKey(
                        null=True,
                        on_delete=django.db.models.deletion.CASCADE,
                        to="users.student",
                    ),
                ),
            ],
            options={
                "unique_together": {("course", "student")},
            },
        ),
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

108-108: Trailing comma missing

Add trailing comma

(COM812)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,157 @@
+# Generated by Django 5.1.4 on 2025-03-19 21:21
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
path: server/backend/courses/migrations/0001_initial.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**UNNECESSARY BLANK LINE DETECTED.**

BLANK LINE AT POSITION 7 IS REDUNDANT. RECOMMEND REMOVAL FOR OPTIMAL CODE EFFICIENCY.

```diff
-
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion

`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,69 +1,37 @@
 import pytest
-from users.models import Instructor, Student, User
+from users.models import User, Student, Instructor
 
 @pytest.mark.django_db
-class TestUserModel:
-    def test_user_creation(self):
-        user = User.objects.create_user(username="testuser", password="password123", role="student")
-        print(f"Created user with id: {user}")
-        assert user.username == "testuser"
-        assert user.role == "student"
-        assert user.check_password("password123")
-
-    def test_user_str(self):
-        user = User.objects.create_user(username="testuser", password="password123", role="student")
-        print(f"Created user with id: {user.id}")
-        assert str(user) == "testuser"
-
-
-@pytest.mark.django_db
-class TestInstructorModel:
-    def test_instructor_creation(self):
-        user = User.objects.create_user(username="test_instructor", password="password123", role="instructor")
-        print(f"Created user with id: {user.id}")
-        instructor = Instructor.objects.create(user=user, designation="Professor", university="MIT")
-        
-        assert instructor.user.username == "test_instructor"
-        assert instructor.designation == "Professor"
-        assert instructor.university == "MIT"
-
-#     def test_instructor_str(self):
-#         # Ensure no previous instructor exists
-#         Instructor.objects.all().delete()
-
-#         user = get_user_model().objects.create_user(
-#             username="instructor1", password="password123", role="instructor"
-#         )
-#         instructor = Instructor.objects.create(
-#             user=user, designation="Professor", university="ABC University"
-#         )
-#         assert str(instructor) == "instructor1"
-
-
-# @pytest.mark.django_db
-# class TestStudentModel:
-#     def test_student_creation(self):
-#         # Ensure no previous student exists
-#         Student.objects.all().delete()
-
-#         user = get_user_model().objects.create_user(
-#             username="student1", password="password123", role="student"
-#         )
-#         student = Student.objects.create(
-#             user=user, course_completed=3, course_enrolled=5
-#         )
-#         assert student.user.username == "student1"
-#         assert student.course_completed == 3
-#         assert student.course_enrolled == 5
-
-#     def test_student_str(self):
-#         # Ensure no previous student exists
-#         Student.objects.all().delete()
-
-#         user = get_user_model().objects.create_user(
-#             username="student1", password="password123", role="student"
-#         )
-#         student = Student.objects.create(
-#             user=user, course_completed=3, course_enrolled=5
-#         )
-#         assert str(student) == "student1"
+class TestUserModels:
+
+    def test_create_student_user(self):
+        user = User.objects.create_user(username='student1', password='testpass123', role='student')
+        print('user id', user.id)
+        print("\nNumber of users in database:", User.objects.count())        
+        student = Student.objects.get(user=user)        
+        student.course_completed = 2
+        student.course_enrolled = 5
+        student.save()
+        assert student.user == user
+        assert student.course_completed == 2
+        assert student.course_enrolled == 5
+        assert user.role == 'student'
+        assert student.user.username == 'student1'
+
+    def test_create_instructor_user(self):
+        user = User.objects.create_user(username='instructor1', password='testpass123', role='instructor')
+        print('user id', user.id)
+        print("\nNumber of users in database:", User.objects.count())        
+        instructor = Instructor.objects.get(user=user)        
+        instructor.designation = 'Professor'
+        instructor.university = 'University of Science'
+        instructor.save()
+        assert instructor.user == user
+        assert instructor.designation == 'Professor'
+        assert instructor.university == 'University of Science'
+        assert user.role == 'instructor'
+        assert instructor.user.username == 'instructor1'
+
path: server/backend/users/tests/test_models.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**INSTRUCTOR USER CREATION TEST VALIDATED.**

TEST CREATES INSTRUCTOR USER AND VERIFIES MODEL ATTRIBUTES. DEBUGGING PRINT STATEMENTS DETECTED - THESE SHOULD BE REMOVED IN PRODUCTION CODE.

```diff
-        print('user id', user.id)
-        print("\nNumber of users in database:", User.objects.count())        
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    def test_create_instructor_user(self):
        user = User.objects.create_user(username='instructor1', password='testpass123', role='instructor')
        instructor = Instructor.objects.get(user=user)        
        instructor.designation = 'Professor'
        instructor.university = 'University of Science'
        instructor.save()
        assert instructor.user == user
        assert instructor.designation == 'Professor'
        assert instructor.university == 'University of Science'
        assert user.role == 'instructor'
        assert instructor.user.username == 'instructor1'
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

21-21: Missing return type annotation for public function `test_create_instructor_user`

Add return type annotation: `None`

(ANN201)

---

22-22: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,69 +1,37 @@
 import pytest
-from users.models import Instructor, Student, User
+from users.models import User, Student, Instructor
 
 @pytest.mark.django_db
-class TestUserModel:
-    def test_user_creation(self):
-        user = User.objects.create_user(username="testuser", password="password123", role="student")
-        print(f"Created user with id: {user}")
-        assert user.username == "testuser"
-        assert user.role == "student"
-        assert user.check_password("password123")
-
-    def test_user_str(self):
-        user = User.objects.create_user(username="testuser", password="password123", role="student")
-        print(f"Created user with id: {user.id}")
-        assert str(user) == "testuser"
-
-
-@pytest.mark.django_db
-class TestInstructorModel:
-    def test_instructor_creation(self):
-        user = User.objects.create_user(username="test_instructor", password="password123", role="instructor")
-        print(f"Created user with id: {user.id}")
-        instructor = Instructor.objects.create(user=user, designation="Professor", university="MIT")
-        
-        assert instructor.user.username == "test_instructor"
-        assert instructor.designation == "Professor"
-        assert instructor.university == "MIT"
-
-#     def test_instructor_str(self):
-#         # Ensure no previous instructor exists
-#         Instructor.objects.all().delete()
-
-#         user = get_user_model().objects.create_user(
-#             username="instructor1", password="password123", role="instructor"
-#         )
-#         instructor = Instructor.objects.create(
-#             user=user, designation="Professor", university="ABC University"
-#         )
-#         assert str(instructor) == "instructor1"
-
-
-# @pytest.mark.django_db
-# class TestStudentModel:
-#     def test_student_creation(self):
-#         # Ensure no previous student exists
-#         Student.objects.all().delete()
-
-#         user = get_user_model().objects.create_user(
-#             username="student1", password="password123", role="student"
-#         )
-#         student = Student.objects.create(
-#             user=user, course_completed=3, course_enrolled=5
-#         )
-#         assert student.user.username == "student1"
-#         assert student.course_completed == 3
-#         assert student.course_enrolled == 5
-
-#     def test_student_str(self):
-#         # Ensure no previous student exists
-#         Student.objects.all().delete()
-
-#         user = get_user_model().objects.create_user(
-#             username="student1", password="password123", role="student"
-#         )
-#         student = Student.objects.create(
-#             user=user, course_completed=3, course_enrolled=5
-#         )
-#         assert str(student) == "student1"
+class TestUserModels:
+
+    def test_create_student_user(self):
+        user = User.objects.create_user(username='student1', password='testpass123', role='student')
+        print('user id', user.id)
+        print("\nNumber of users in database:", User.objects.count())        
+        student = Student.objects.get(user=user)        
+        student.course_completed = 2
+        student.course_enrolled = 5
+        student.save()
+        assert student.user == user
+        assert student.course_completed == 2
+        assert student.course_enrolled == 5
+        assert user.role == 'student'
+        assert student.user.username == 'student1'
+
path: server/backend/users/tests/test_models.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**STUDENT USER CREATION TEST ANALYSIS COMPLETE.**

TEST CREATES STUDENT USER AND VERIFIES MODEL ATTRIBUTES. DEBUGGING PRINT STATEMENTS DETECTED - THESE SHOULD BE REMOVED IN PRODUCTION CODE.

```diff
-        print('user id', user.id)
-        print("\nNumber of users in database:", User.objects.count())        
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    def test_create_student_user(self):
        user = User.objects.create_user(username='student1', password='testpass123', role='student')
        student = Student.objects.get(user=user)        
        student.course_completed = 2
        student.course_enrolled = 5
        student.save()
        assert student.user == user
        assert student.course_completed == 2
        assert student.course_enrolled == 5
        assert user.role == 'student'
        assert student.user.username == 'student1'
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

7-7: Missing return type annotation for public function `test_create_student_user`

Add return type annotation: `None`

(ANN201)

---

8-8: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,49 @@
+import pytest
+from rest_framework.test import APIClient
+from rest_framework import status
+from django.contrib.auth import get_user_model
+from quiz.models import Category, Question, Option, QuizAttempt, UserAnswer
path: server/backend/quiz/tests/test_models.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**TEST IMPORTS REVIEW ALERT: UNUSED IMPORTS DETECTED.**

IMPORTS FROM REST_FRAMEWORK NOT UTILIZED IN TEST FUNCTIONS. RECOMMEND REMOVAL OF UNUSED MODULES.

```diff
 import pytest
-from rest_framework.test import APIClient
-from rest_framework import status
 from django.contrib.auth import get_user_model
 from quiz.models import Category, Question, Option, QuizAttempt, UserAnswer
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import pytest
from django.contrib.auth import get_user_model
from quiz.models import Category, Question, Option, QuizAttempt, UserAnswer
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

2-2: `rest_framework.test.APIClient` imported but unused

Remove unused import: `rest_framework.test.APIClient`

(F401)

---

3-3: `rest_framework.status` imported but unused

Remove unused import: `rest_framework.status`

(F401)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+import pytest
+from rest_framework_simplejwt.tokens import RefreshToken, AccessToken
+from users.models import User
+from users.serializers import CustomTokenObtainPairSerializer, CustomTokenRefreshSerializer, DashboardStatsSerializer
+
+@pytest.mark.django_db
+class TestCustomTokenSerializers:
+
+    def test_custom_token_obtain_pair_serializer(self):
+        user = User.objects.create_user(username='testuser', password='testpass123', role='student', email='testuser@example.com')
+        serializer = CustomTokenObtainPairSerializer(data={'username': 'testuser', 'password': 'testpass123'})
+        assert serializer.is_valid()
+        
+        token = serializer.validated_data['access']
+        
+        # Decode the access token
+        decoded_token = AccessToken(token).payload
+        
+        # Assert that the custom fields are in the decoded token
+        assert 'role' in decoded_token
+        assert decoded_token['role'] == user.role
+        assert 'is_superuser' in decoded_token
+        assert decoded_token['is_superuser'] == user.is_superuser
path: server/backend/users/tests/test_serializers_token_dashboard.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**FUNCTION LOGIC APPROVED. TYPE ANNOTATION MISSING.**

TEST FUNCTION CORRECTLY VALIDATES CUSTOM TOKEN SERIALIZER FUNCTIONALITY. TOKEN PAYLOAD VERIFICATION SEQUENCE OPERATIONAL.

```diff
-    def test_custom_token_obtain_pair_serializer(self):
+    def test_custom_token_obtain_pair_serializer(self) -> None:
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    def test_custom_token_obtain_pair_serializer(self) -> None:
        user = User.objects.create_user(username='testuser', password='testpass123', role='student', email='testuser@example.com')
        serializer = CustomTokenObtainPairSerializer(data={'username': 'testuser', 'password': 'testpass123'})
        assert serializer.is_valid()
        
        token = serializer.validated_data['access']
        
        # Decode the access token
        decoded_token = AccessToken(token).payload
        
        # Assert that the custom fields are in the decoded token
        assert 'role' in decoded_token
        assert decoded_token['role'] == user.role
        assert 'is_superuser' in decoded_token
        assert decoded_token['is_superuser'] == user.is_superuser
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

9-9: Missing return type annotation for public function `test_custom_token_obtain_pair_serializer`

Add return type annotation: `None`

(ANN201)

---

10-10: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+import pytest
+from rest_framework_simplejwt.tokens import RefreshToken, AccessToken
+from users.models import User
+from users.serializers import CustomTokenObtainPairSerializer, CustomTokenRefreshSerializer, DashboardStatsSerializer
+
+@pytest.mark.django_db
+class TestCustomTokenSerializers:
+
+    def test_custom_token_obtain_pair_serializer(self):
+        user = User.objects.create_user(username='testuser', password='testpass123', role='student', email='testuser@example.com')
+        serializer = CustomTokenObtainPairSerializer(data={'username': 'testuser', 'password': 'testpass123'})
+        assert serializer.is_valid()
+        
+        token = serializer.validated_data['access']
+        
+        # Decode the access token
+        decoded_token = AccessToken(token).payload
+        
+        # Assert that the custom fields are in the decoded token
+        assert 'role' in decoded_token
+        assert decoded_token['role'] == user.role
+        assert 'is_superuser' in decoded_token
+        assert decoded_token['is_superuser'] == user.is_superuser
+
+    def test_custom_token_refresh_serializer(self):
+        user = User.objects.create_user(username='testuser', password='testpass123', role='student', email='testuser@example.com')
+        refresh = RefreshToken.for_user(user)
+        data = {'refresh': str(refresh)}
+        serializer = CustomTokenRefreshSerializer(data=data)
+        assert serializer.is_valid()
+        
+        access_token = serializer.validated_data['access']
+        
+        # Decode the access token (not the refresh token)
+        decoded_token = AccessToken(access_token).payload
+        
+        # Assert that the custom fields are in the decoded token
+        assert 'role' in decoded_token
+        assert decoded_token['role'] == user.role
+        assert 'is_superuser' in decoded_token
+        assert decoded_token['is_superuser'] == user.is_superuser
path: server/backend/users/tests/test_serializers_token_dashboard.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**FUNCTION LOGIC APPROVED. TYPE ANNOTATION MISSING.**

TEST FUNCTION CORRECTLY VALIDATES REFRESH SERIALIZER FUNCTIONALITY. DECODED TOKEN VERIFICATION SEQUENCE OPERATIONAL.

```diff
-    def test_custom_token_refresh_serializer(self):
+    def test_custom_token_refresh_serializer(self) -> None:
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    def test_custom_token_refresh_serializer(self) -> None:
        user = User.objects.create_user(username='testuser', password='testpass123', role='student', email='testuser@example.com')
        refresh = RefreshToken.for_user(user)
        data = {'refresh': str(refresh)}
        serializer = CustomTokenRefreshSerializer(data=data)
        assert serializer.is_valid()
        
        access_token = serializer.validated_data['access']
        
        # Decode the access token (not the refresh token)
        decoded_token = AccessToken(access_token).payload
        
        # Assert that the custom fields are in the decoded token
        assert 'role' in decoded_token
        assert decoded_token['role'] == user.role
        assert 'is_superuser' in decoded_token
        assert decoded_token['is_superuser'] == user.is_superuser
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

25-25: Missing return type annotation for public function `test_custom_token_refresh_serializer`

Add return type annotation: `None`

(ANN201)

---

26-26: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+import pytest
+from rest_framework_simplejwt.tokens import RefreshToken, AccessToken
+from users.models import User
+from users.serializers import CustomTokenObtainPairSerializer, CustomTokenRefreshSerializer, DashboardStatsSerializer
+
+@pytest.mark.django_db
+class TestCustomTokenSerializers:
+
+    def test_custom_token_obtain_pair_serializer(self):
+        user = User.objects.create_user(username='testuser', password='testpass123', role='student', email='testuser@example.com')
+        serializer = CustomTokenObtainPairSerializer(data={'username': 'testuser', 'password': 'testpass123'})
+        assert serializer.is_valid()
+        
+        token = serializer.validated_data['access']
+        
+        # Decode the access token
+        decoded_token = AccessToken(token).payload
+        
+        # Assert that the custom fields are in the decoded token
+        assert 'role' in decoded_token
+        assert decoded_token['role'] == user.role
+        assert 'is_superuser' in decoded_token
+        assert decoded_token['is_superuser'] == user.is_superuser
+
+    def test_custom_token_refresh_serializer(self):
+        user = User.objects.create_user(username='testuser', password='testpass123', role='student', email='testuser@example.com')
+        refresh = RefreshToken.for_user(user)
+        data = {'refresh': str(refresh)}
+        serializer = CustomTokenRefreshSerializer(data=data)
+        assert serializer.is_valid()
+        
+        access_token = serializer.validated_data['access']
+        
+        # Decode the access token (not the refresh token)
+        decoded_token = AccessToken(access_token).payload
+        
+        # Assert that the custom fields are in the decoded token
+        assert 'role' in decoded_token
+        assert decoded_token['role'] == user.role
+        assert 'is_superuser' in decoded_token
+        assert decoded_token['is_superuser'] == user.is_superuser
+
+
+@pytest.mark.django_db
+class TestDashboardStatsSerializer:
+
+    def test_dashboard_stats_serializer(self):
+        user1 = User.objects.create_user(username='student1', password='testpass123', role='student', email='student1@example.com')
+        user2 = User.objects.create_user(username='instructor1', password='testpass123', role='instructor', email='instructor1@example.com')
+        course_data = {
+            'total_users': 2,
+            'total_students': 1,
+            'total_instructors': 1,
+            'total_courses': 0,
+            'total_contents': 0,
+            'total_quizzes': 0,
+        }
+        serializer = DashboardStatsSerializer(data=course_data)
+        assert serializer.is_valid()
+        stats = serializer.validated_data
+        assert stats['total_users'] == 2
+        assert stats['total_students'] == 1
+        assert stats['total_instructors'] == 1
+        assert stats['total_courses'] == 0
+        assert stats['total_contents'] == 0
+        assert stats['total_quizzes'] == 0
path: server/backend/users/tests/test_serializers_token_dashboard.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**FUNCTION LOGIC APPROVED. UNUSED VARIABLES DETECTED.**

TEST FUNCTION CORRECTLY VALIDATES DASHBOARD STATS SERIALIZER. VARIABLES 'user1' AND 'user2' CREATED BUT NOT DIRECTLY REFERENCED. IMPLEMENTATION CORRECT AS USERS ARE CREATED FOR SETUP.

```diff
-    def test_dashboard_stats_serializer(self):
+    def test_dashboard_stats_serializer(self) -> None:
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    def test_dashboard_stats_serializer(self) -> None:
        user1 = User.objects.create_user(username='student1', password='testpass123', role='student', email='student1@example.com')
        user2 = User.objects.create_user(username='instructor1', password='testpass123', role='instructor', email='instructor1@example.com')
        course_data = {
            'total_users': 2,
            'total_students': 1,
            'total_instructors': 1,
            'total_courses': 0,
            'total_contents': 0,
            'total_quizzes': 0,
        }
        serializer = DashboardStatsSerializer(data=course_data)
        assert serializer.is_valid()
        stats = serializer.validated_data
        assert stats['total_users'] == 2
        assert stats['total_students'] == 1
        assert stats['total_instructors'] == 1
        assert stats['total_courses'] == 0
        assert stats['total_contents'] == 0
        assert stats['total_quizzes'] == 0
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

47-47: Missing return type annotation for public function `test_dashboard_stats_serializer`

Add return type annotation: `None`

(ANN201)

---

48-48: Local variable `user1` is assigned to but never used

Remove assignment to unused variable `user1`

(F841)

---

48-48: Possible hardcoded password assigned to argument: "password"

(S106)

---

49-49: Local variable `user2` is assigned to but never used

Remove assignment to unused variable `user2`

(F841)

---

49-49: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:57:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+import pytest
+from rest_framework.exceptions import ValidationError
+from users.models import User, Instructor
+from users.serializers import UserSerializer, InstructorSerializer
+
+@pytest.mark.django_db
+class TestUserSerializers:
+
+    def test_user_serializer(self):
+        user = User.objects.create_user(username='testuser', password='testpass123', role='student', email='testuser@example.com', bio='This is a test bio.')
+        serializer = UserSerializer(user)
+        assert serializer.data['id'] == user.id
+        assert serializer.data['username'] == user.username
+        assert serializer.data['email'] == user.email
+        assert serializer.data['role'] == user.role
+        assert serializer.data['bio'] == user.bio
+        assert serializer.data['is_superuser'] == user.is_superuser
path: server/backend/users/tests/test_serializers_user.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**FUNCTION LOGIC APPROVED. TYPE ANNOTATION MISSING.**

TEST FUNCTION CORRECTLY VALIDATES USER SERIALIZER. ALL USER ATTRIBUTES CORRECTLY VERIFIED.

```diff
-    def test_user_serializer(self):
+    def test_user_serializer(self) -> None:
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    def test_user_serializer(self) -> None:
        user = User.objects.create_user(username='testuser', password='testpass123', role='student', email='testuser@example.com', bio='This is a test bio.')
        serializer = UserSerializer(user)
        assert serializer.data['id'] == user.id
        assert serializer.data['username'] == user.username
        assert serializer.data['email'] == user.email
        assert serializer.data['role'] == user.role
        assert serializer.data['bio'] == user.bio
        assert serializer.data['is_superuser'] == user.is_superuser
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

9-9: Missing return type annotation for public function `test_user_serializer`

Add return type annotation: `None`

(ANN201)

---

10-10: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:58:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+import pytest
+from rest_framework.exceptions import ValidationError
+from users.models import User, Instructor
+from users.serializers import UserSerializer, InstructorSerializer
+
+@pytest.mark.django_db
+class TestUserSerializers:
+
+    def test_user_serializer(self):
+        user = User.objects.create_user(username='testuser', password='testpass123', role='student', email='testuser@example.com', bio='This is a test bio.')
+        serializer = UserSerializer(user)
+        assert serializer.data['id'] == user.id
+        assert serializer.data['username'] == user.username
+        assert serializer.data['email'] == user.email
+        assert serializer.data['role'] == user.role
+        assert serializer.data['bio'] == user.bio
+        assert serializer.data['is_superuser'] == user.is_superuser
+
+    def test_instructor_serializer(self):
+        user = User.objects.create_user(username='instructor1', password='testpass123', role='instructor', email='instructor1@example.com')
+        instructor = Instructor.objects.get(user=user)
+        serializer = InstructorSerializer(instructor)
+        assert serializer.data['id'] == instructor.id
+        assert serializer.data['name'] == user.username
+        assert serializer.data['designation'] == instructor.designation
+        assert serializer.data['university'] == instructor.university
+
+    def test_instructor_serializer_missing_user(self):
+        instructor = Instructor(designation='Professor', university='University of Science')
+        try:
+            InstructorSerializer(instructor).data
+        except ValidationError as e:
+            assert 'user' in str(e)
+
+    def test_user_serializer_invalid_data(self):
+        invalid_data = {
+            'username': '',
+            'email': 'invalidemail',
+        }
+        serializer = UserSerializer(data=invalid_data)
+        assert not serializer.is_valid()
+        assert 'username' in serializer.errors
+        assert 'email' in serializer.errors
path: server/backend/users/tests/test_serializers_user.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**FUNCTION LOGIC APPROVED. TYPE ANNOTATION MISSING.**

TEST FUNCTION CORRECTLY VALIDATES SERIALIZER ERROR HANDLING FOR INVALID DATA.

```diff
-    def test_user_serializer_invalid_data(self):
+    def test_user_serializer_invalid_data(self) -> None:
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    def test_user_serializer_invalid_data(self) -> None:
        invalid_data = {
            'username': '',
            'email': 'invalidemail',
        }
        serializer = UserSerializer(data=invalid_data)
        assert not serializer.is_valid()
        assert 'username' in serializer.errors
        assert 'email' in serializer.errors
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

35-35: Missing return type annotation for public function `test_user_serializer_invalid_data`

Add return type annotation: `None`

(ANN201)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:58:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+import pytest
+from rest_framework.exceptions import ValidationError
+from users.models import User, Instructor
+from users.serializers import UserSerializer, InstructorSerializer
+
+@pytest.mark.django_db
+class TestUserSerializers:
+
+    def test_user_serializer(self):
+        user = User.objects.create_user(username='testuser', password='testpass123', role='student', email='testuser@example.com', bio='This is a test bio.')
+        serializer = UserSerializer(user)
+        assert serializer.data['id'] == user.id
+        assert serializer.data['username'] == user.username
+        assert serializer.data['email'] == user.email
+        assert serializer.data['role'] == user.role
+        assert serializer.data['bio'] == user.bio
+        assert serializer.data['is_superuser'] == user.is_superuser
+
+    def test_instructor_serializer(self):
+        user = User.objects.create_user(username='instructor1', password='testpass123', role='instructor', email='instructor1@example.com')
+        instructor = Instructor.objects.get(user=user)
+        serializer = InstructorSerializer(instructor)
+        assert serializer.data['id'] == instructor.id
+        assert serializer.data['name'] == user.username
+        assert serializer.data['designation'] == instructor.designation
+        assert serializer.data['university'] == instructor.university
+
path: server/backend/users/tests/test_serializers_user.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**FUNCTION LOGIC APPROVED. TYPE ANNOTATION MISSING.**

TEST FUNCTION CORRECTLY VALIDATES INSTRUCTOR SERIALIZER. ALL INSTRUCTOR ATTRIBUTES CORRECTLY VERIFIED.

```diff
-    def test_instructor_serializer(self):
+    def test_instructor_serializer(self) -> None:
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    def test_instructor_serializer(self) -> None:
        user = User.objects.create_user(username='instructor1', password='testpass123', role='instructor', email='instructor1@example.com')
        instructor = Instructor.objects.get(user=user)
        serializer = InstructorSerializer(instructor)
        assert serializer.data['id'] == instructor.id
        assert serializer.data['name'] == user.username
        assert serializer.data['designation'] == instructor.designation
        assert serializer.data['university'] == instructor.university
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

19-19: Missing return type annotation for public function `test_instructor_serializer`

Add return type annotation: `None`

(ANN201)

---

20-20: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:58:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+import pytest
+from rest_framework.exceptions import ValidationError
+from users.models import User, Instructor
+from users.serializers import UserSerializer, InstructorSerializer
+
+@pytest.mark.django_db
+class TestUserSerializers:
+
+    def test_user_serializer(self):
+        user = User.objects.create_user(username='testuser', password='testpass123', role='student', email='testuser@example.com', bio='This is a test bio.')
+        serializer = UserSerializer(user)
+        assert serializer.data['id'] == user.id
+        assert serializer.data['username'] == user.username
+        assert serializer.data['email'] == user.email
+        assert serializer.data['role'] == user.role
+        assert serializer.data['bio'] == user.bio
+        assert serializer.data['is_superuser'] == user.is_superuser
+
+    def test_instructor_serializer(self):
+        user = User.objects.create_user(username='instructor1', password='testpass123', role='instructor', email='instructor1@example.com')
+        instructor = Instructor.objects.get(user=user)
+        serializer = InstructorSerializer(instructor)
+        assert serializer.data['id'] == instructor.id
+        assert serializer.data['name'] == user.username
+        assert serializer.data['designation'] == instructor.designation
+        assert serializer.data['university'] == instructor.university
+
+    def test_instructor_serializer_missing_user(self):
+        instructor = Instructor(designation='Professor', university='University of Science')
+        try:
+            InstructorSerializer(instructor).data
+        except ValidationError as e:
+            assert 'user' in str(e)
+
path: server/backend/users/tests/test_serializers_user.py
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**ERROR HANDLING TEST REQUIRES OPTIMIZATION.**

TEST CORRECTLY VALIDATES BEHAVIOR WITH MISSING USER, BUT PYTEST PROVIDES MORE EFFICIENT METHOD FOR TESTING EXCEPTIONS.

```diff
-    def test_instructor_serializer_missing_user(self):
-        instructor = Instructor(designation='Professor', university='University of Science')
-        try:
-            InstructorSerializer(instructor).data
-        except ValidationError as e:
-            assert 'user' in str(e)
+    def test_instructor_serializer_missing_user(self) -> None:
+        instructor = Instructor(designation='Professor', university='University of Science')
+        with pytest.raises(ValidationError) as excinfo:
+            InstructorSerializer(instructor).data
+        assert 'user' in str(excinfo.value)
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    def test_instructor_serializer_missing_user(self) -> None:
        instructor = Instructor(designation='Professor', university='University of Science')
        with pytest.raises(ValidationError) as excinfo:
            InstructorSerializer(instructor).data
        assert 'user' in str(excinfo.value)
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

28-28: Missing return type annotation for public function `test_instructor_serializer_missing_user`

Add return type annotation: `None`

(ANN201)

---

31-31: Found useless attribute access. Either assign it to a variable or remove it.

(B018)

---

33-33: Found assertion on exception `e` in `except` block, use `pytest.raises()` instead

(PT017)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:58:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,92 @@
+import pytest
+from rest_framework.test import APIClient
+from rest_framework_simplejwt.tokens import RefreshToken
+from rest_framework import status
+from users.models import Instructor, User
+
+@pytest.mark.django_db
+class TestAuthViews:
+    client = APIClient()
+    def test_register_view(self):
+        data = {
+            'username': 'testuser',
+            'email': 'testuser@example.com',
+            'password': 'password123',
+            'role': 'student',
+        }
+        response = self.client.post('/register/', data, format='json')        
+        assert response.status_code == status.HTTP_201_CREATED
+        assert 'user' in response.data
+        assert 'refresh' in response.data
+        assert 'access' in response.data
+        user = User.objects.get(username='testuser')
+        assert user.email == 'testuser@example.com'
+
+    def test_login_view(self):
+        data = {
+            'username': 'testuser',
+            'password': 'password123',
+        }
+        user = User.objects.create_user(username='testuser', password='password123', email='testuser@example.com', role='student')
+
+        response = self.client.post('/login/', data, format='json')
+
+        assert response.status_code == status.HTTP_200_OK
+        assert 'refresh' in response.data
+        assert 'access' in response.data
+        assert 'user' in response.data
+
+    def test_logout_view(self):
+        user = User.objects.create_user(username='testuser', password='password123', email='testuser@example.com', role='student')
+        
+        # Generate refresh token
+        refresh_token = str(RefreshToken.for_user(user))
+        response = self.client.post('/logout/', {'refresh': refresh_token}, format='json')
+        
+        assert response.status_code == status.HTTP_205_RESET_CONTENT
+        assert response.data.get("message") == "Logout successful"
+
+    def test_logout_view_no_token(self):
+        response = self.client.post('/logout/', {}, format='json')
+        
+        assert response.status_code == status.HTTP_400_BAD_REQUEST
+        assert response.data.get("error") == "Refresh token is required"
+
+    def test_dashboard_stats_view(self):
+        admin_user = User.objects.create_superuser(username='adminuser', password='password123', email='admin@example.com')        
+        self.client.force_authenticate(user=admin_user)
+
+        response = self.client.get('/dashboard/stats/', format='json')
+        
+        assert response.status_code == status.HTTP_200_OK
+        assert 'success' in response.data
+        assert response.data.get('success') is True
+        assert 'data' in response.data
+        assert 'total_users' in response.data['data']
+        assert 'total_students' in response.data['data']
+
+    def test_dashboard_stats_view_no_permission(self):
+        normal_user = User.objects.create_user(username='normaluser', password='password123', email='normal@example.com')
+        self.client.force_authenticate(user=normal_user)
+
+        response = self.client.get('/dashboard/stats/', format='json')
+        
+        assert response.status_code == status.HTTP_403_FORBIDDEN
+        assert 'detail' in response.data
+        assert response.data['detail'] == 'You do not have permission to perform this action.'
path: server/backend/users/tests/test_views.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**PERMISSION DENIED TEST APPROVED.**

TEST CORRECTLY VALIDATES ACCESS CONTROL FOR DASHBOARD STATS ENDPOINT. PERMISSION DENIAL CORRECTLY VERIFIED.

```diff
-    def test_dashboard_stats_view_no_permission(self):
+    def test_dashboard_stats_view_no_permission(self) -> None:
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    def test_dashboard_stats_view_no_permission(self) -> None:
        normal_user = User.objects.create_user(username='normaluser', password='password123', email='normal@example.com')
        self.client.force_authenticate(user=normal_user)

        response = self.client.get('/dashboard/stats/', format='json')
        
        assert response.status_code == status.HTTP_403_FORBIDDEN
        assert 'detail' in response.data
        assert response.data['detail'] == 'You do not have permission to perform this action.'
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

68-68: Missing return type annotation for public function `test_dashboard_stats_view_no_permission`

Add return type annotation: `None`

(ANN201)

---

69-69: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:58:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,92 @@
+import pytest
+from rest_framework.test import APIClient
+from rest_framework_simplejwt.tokens import RefreshToken
+from rest_framework import status
+from users.models import Instructor, User
+
+@pytest.mark.django_db
+class TestAuthViews:
+    client = APIClient()
+    def test_register_view(self):
+        data = {
+            'username': 'testuser',
+            'email': 'testuser@example.com',
+            'password': 'password123',
+            'role': 'student',
+        }
+        response = self.client.post('/register/', data, format='json')        
+        assert response.status_code == status.HTTP_201_CREATED
+        assert 'user' in response.data
+        assert 'refresh' in response.data
+        assert 'access' in response.data
+        user = User.objects.get(username='testuser')
+        assert user.email == 'testuser@example.com'
+
+    def test_login_view(self):
+        data = {
+            'username': 'testuser',
+            'password': 'password123',
+        }
+        user = User.objects.create_user(username='testuser', password='password123', email='testuser@example.com', role='student')
+
+        response = self.client.post('/login/', data, format='json')
+
+        assert response.status_code == status.HTTP_200_OK
+        assert 'refresh' in response.data
+        assert 'access' in response.data
+        assert 'user' in response.data
+
path: server/backend/users/tests/test_views.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**LOGIN TEST APPROVED. UNUSED VARIABLE WARNING.**

TEST CORRECTLY VALIDATES LOGIN ENDPOINT FUNCTIONALITY. VARIABLE 'user' IS CREATED BUT REFERENCE NOT STORED. THIS IS ACCEPTABLE AS USER CREATION IS REQUIRED FOR TEST SETUP.

```diff
-    def test_login_view(self):
+    def test_login_view(self) -> None:
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    def test_login_view(self) -> None:
        data = {
            'username': 'testuser',
            'password': 'password123',
        }
        user = User.objects.create_user(username='testuser', password='password123', email='testuser@example.com', role='student')

        response = self.client.post('/login/', data, format='json')

        assert response.status_code == status.HTTP_200_OK
        assert 'refresh' in response.data
        assert 'access' in response.data
        assert 'user' in response.data
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

25-25: Missing return type annotation for public function `test_login_view`

Add return type annotation: `None`

(ANN201)

---

30-30: Local variable `user` is assigned to but never used

Remove assignment to unused variable `user`

(F841)

---

30-30: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:58:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,92 @@
+import pytest
+from rest_framework.test import APIClient
+from rest_framework_simplejwt.tokens import RefreshToken
+from rest_framework import status
+from users.models import Instructor, User
+
+@pytest.mark.django_db
+class TestAuthViews:
+    client = APIClient()
+    def test_register_view(self):
+        data = {
+            'username': 'testuser',
+            'email': 'testuser@example.com',
+            'password': 'password123',
+            'role': 'student',
+        }
+        response = self.client.post('/register/', data, format='json')        
+        assert response.status_code == status.HTTP_201_CREATED
+        assert 'user' in response.data
+        assert 'refresh' in response.data
+        assert 'access' in response.data
+        user = User.objects.get(username='testuser')
+        assert user.email == 'testuser@example.com'
+
+    def test_login_view(self):
+        data = {
+            'username': 'testuser',
+            'password': 'password123',
+        }
+        user = User.objects.create_user(username='testuser', password='password123', email='testuser@example.com', role='student')
+
+        response = self.client.post('/login/', data, format='json')
+
+        assert response.status_code == status.HTTP_200_OK
+        assert 'refresh' in response.data
+        assert 'access' in response.data
+        assert 'user' in response.data
+
+    def test_logout_view(self):
+        user = User.objects.create_user(username='testuser', password='password123', email='testuser@example.com', role='student')
+        
+        # Generate refresh token
+        refresh_token = str(RefreshToken.for_user(user))
+        response = self.client.post('/logout/', {'refresh': refresh_token}, format='json')
+        
+        assert response.status_code == status.HTTP_205_RESET_CONTENT
+        assert response.data.get("message") == "Logout successful"
+
+    def test_logout_view_no_token(self):
+        response = self.client.post('/logout/', {}, format='json')
+        
+        assert response.status_code == status.HTTP_400_BAD_REQUEST
+        assert response.data.get("error") == "Refresh token is required"
path: server/backend/users/tests/test_views.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**LOGOUT ERROR TEST APPROVED.**

TEST CORRECTLY VALIDATES LOGOUT ENDPOINT ERROR HANDLING WITH MISSING TOKEN.

```diff
-    def test_logout_view_no_token(self):
+    def test_logout_view_no_token(self) -> None:
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    def test_logout_view_no_token(self) -> None:
        response = self.client.post('/logout/', {}, format='json')
        
        assert response.status_code == status.HTTP_400_BAD_REQUEST
        assert response.data.get("error") == "Refresh token is required"
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

49-49: Missing return type annotation for public function `test_logout_view_no_token`

Add return type annotation: `None`

(ANN201)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:58:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,92 @@
+import pytest
+from rest_framework.test import APIClient
+from rest_framework_simplejwt.tokens import RefreshToken
+from rest_framework import status
+from users.models import Instructor, User
+
+@pytest.mark.django_db
+class TestAuthViews:
+    client = APIClient()
+    def test_register_view(self):
+        data = {
+            'username': 'testuser',
+            'email': 'testuser@example.com',
+            'password': 'password123',
+            'role': 'student',
+        }
+        response = self.client.post('/register/', data, format='json')        
+        assert response.status_code == status.HTTP_201_CREATED
+        assert 'user' in response.data
+        assert 'refresh' in response.data
+        assert 'access' in response.data
+        user = User.objects.get(username='testuser')
+        assert user.email == 'testuser@example.com'
+
+    def test_login_view(self):
+        data = {
+            'username': 'testuser',
+            'password': 'password123',
+        }
+        user = User.objects.create_user(username='testuser', password='password123', email='testuser@example.com', role='student')
+
+        response = self.client.post('/login/', data, format='json')
+
+        assert response.status_code == status.HTTP_200_OK
+        assert 'refresh' in response.data
+        assert 'access' in response.data
+        assert 'user' in response.data
+
+    def test_logout_view(self):
+        user = User.objects.create_user(username='testuser', password='password123', email='testuser@example.com', role='student')
+        
+        # Generate refresh token
+        refresh_token = str(RefreshToken.for_user(user))
+        response = self.client.post('/logout/', {'refresh': refresh_token}, format='json')
+        
+        assert response.status_code == status.HTTP_205_RESET_CONTENT
+        assert response.data.get("message") == "Logout successful"
+
path: server/backend/users/tests/test_views.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**LOGOUT TEST APPROVED.**

TEST CORRECTLY VALIDATES LOGOUT ENDPOINT FUNCTIONALITY WITH VALID TOKEN.

```diff
-    def test_logout_view(self):
+    def test_logout_view(self) -> None:
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    def test_logout_view(self) -> None:
        user = User.objects.create_user(username='testuser', password='password123', email='testuser@example.com', role='student')
        
        # Generate refresh token
        refresh_token = str(RefreshToken.for_user(user))
        response = self.client.post('/logout/', {'refresh': refresh_token}, format='json')
        
        assert response.status_code == status.HTTP_205_RESET_CONTENT
        assert response.data.get("message") == "Logout successful"
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

39-39: Missing return type annotation for public function `test_logout_view`

Add return type annotation: `None`

(ANN201)

---

40-40: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:58:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,92 @@
+import pytest
+from rest_framework.test import APIClient
+from rest_framework_simplejwt.tokens import RefreshToken
+from rest_framework import status
+from users.models import Instructor, User
+
+@pytest.mark.django_db
+class TestAuthViews:
+    client = APIClient()
+    def test_register_view(self):
+        data = {
+            'username': 'testuser',
+            'email': 'testuser@example.com',
+            'password': 'password123',
+            'role': 'student',
+        }
+        response = self.client.post('/register/', data, format='json')        
+        assert response.status_code == status.HTTP_201_CREATED
+        assert 'user' in response.data
+        assert 'refresh' in response.data
+        assert 'access' in response.data
+        user = User.objects.get(username='testuser')
+        assert user.email == 'testuser@example.com'
+
path: server/backend/users/tests/test_views.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**CLIENT INITIALIZATION AND REGISTRATION TEST APPROVED.**

TEST PROPERLY VALIDATES REGISTRATION ENDPOINT FUNCTIONALITY. ALL ASSERTIONS CORRECTLY VERIFY RESPONSE DATA AND USER CREATION.

```diff
-    def test_register_view(self):
+    def test_register_view(self) -> None:
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    client = APIClient()
    def test_register_view(self) -> None:
        data = {
            'username': 'testuser',
            'email': 'testuser@example.com',
            'password': 'password123',
            'role': 'student',
        }
        response = self.client.post('/register/', data, format='json')        
        assert response.status_code == status.HTTP_201_CREATED
        assert 'user' in response.data
        assert 'refresh' in response.data
        assert 'access' in response.data
        user = User.objects.get(username='testuser')
        assert user.email == 'testuser@example.com'
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

10-10: Missing return type annotation for public function `test_register_view`

Add return type annotation: `None`

(ANN201)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:58:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,92 @@
+import pytest
+from rest_framework.test import APIClient
+from rest_framework_simplejwt.tokens import RefreshToken
+from rest_framework import status
+from users.models import Instructor, User
+
+@pytest.mark.django_db
+class TestAuthViews:
+    client = APIClient()
+    def test_register_view(self):
+        data = {
+            'username': 'testuser',
+            'email': 'testuser@example.com',
+            'password': 'password123',
+            'role': 'student',
+        }
+        response = self.client.post('/register/', data, format='json')        
+        assert response.status_code == status.HTTP_201_CREATED
+        assert 'user' in response.data
+        assert 'refresh' in response.data
+        assert 'access' in response.data
+        user = User.objects.get(username='testuser')
+        assert user.email == 'testuser@example.com'
+
+    def test_login_view(self):
+        data = {
+            'username': 'testuser',
+            'password': 'password123',
+        }
+        user = User.objects.create_user(username='testuser', password='password123', email='testuser@example.com', role='student')
+
+        response = self.client.post('/login/', data, format='json')
+
+        assert response.status_code == status.HTTP_200_OK
+        assert 'refresh' in response.data
+        assert 'access' in response.data
+        assert 'user' in response.data
+
+    def test_logout_view(self):
+        user = User.objects.create_user(username='testuser', password='password123', email='testuser@example.com', role='student')
+        
+        # Generate refresh token
+        refresh_token = str(RefreshToken.for_user(user))
+        response = self.client.post('/logout/', {'refresh': refresh_token}, format='json')
+        
+        assert response.status_code == status.HTTP_205_RESET_CONTENT
+        assert response.data.get("message") == "Logout successful"
+
+    def test_logout_view_no_token(self):
+        response = self.client.post('/logout/', {}, format='json')
+        
+        assert response.status_code == status.HTTP_400_BAD_REQUEST
+        assert response.data.get("error") == "Refresh token is required"
+
+    def test_dashboard_stats_view(self):
+        admin_user = User.objects.create_superuser(username='adminuser', password='password123', email='admin@example.com')        
+        self.client.force_authenticate(user=admin_user)
+
+        response = self.client.get('/dashboard/stats/', format='json')
+        
+        assert response.status_code == status.HTTP_200_OK
+        assert 'success' in response.data
+        assert response.data.get('success') is True
+        assert 'data' in response.data
+        assert 'total_users' in response.data['data']
+        assert 'total_students' in response.data['data']
+
path: server/backend/users/tests/test_views.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**DASHBOARD STATS TEST APPROVED.**

TEST CORRECTLY VALIDATES DASHBOARD STATS ENDPOINT FOR AUTHORIZED USERS. ALL NECESSARY RESPONSE FIELDS VERIFIED.

```diff
-    def test_dashboard_stats_view(self):
+    def test_dashboard_stats_view(self) -> None:
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    def test_dashboard_stats_view(self) -> None:
        admin_user = User.objects.create_superuser(username='adminuser', password='password123', email='admin@example.com')        
        self.client.force_authenticate(user=admin_user)

        response = self.client.get('/dashboard/stats/', format='json')
        
        assert response.status_code == status.HTTP_200_OK
        assert 'success' in response.data
        assert response.data.get('success') is True
        assert 'data' in response.data
        assert 'total_users' in response.data['data']
        assert 'total_students' in response.data['data']
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

55-55: Missing return type annotation for public function `test_dashboard_stats_view`

Add return type annotation: `None`

(ANN201)

---

56-56: Possible hardcoded password assigned to argument: "password"

(S106)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:58:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,92 @@
+import pytest
+from rest_framework.test import APIClient
+from rest_framework_simplejwt.tokens import RefreshToken
+from rest_framework import status
+from users.models import Instructor, User
+
+@pytest.mark.django_db
+class TestAuthViews:
+    client = APIClient()
+    def test_register_view(self):
+        data = {
+            'username': 'testuser',
+            'email': 'testuser@example.com',
+            'password': 'password123',
+            'role': 'student',
+        }
+        response = self.client.post('/register/', data, format='json')        
+        assert response.status_code == status.HTTP_201_CREATED
+        assert 'user' in response.data
+        assert 'refresh' in response.data
+        assert 'access' in response.data
+        user = User.objects.get(username='testuser')
+        assert user.email == 'testuser@example.com'
+
+    def test_login_view(self):
+        data = {
+            'username': 'testuser',
+            'password': 'password123',
+        }
+        user = User.objects.create_user(username='testuser', password='password123', email='testuser@example.com', role='student')
+
+        response = self.client.post('/login/', data, format='json')
+
+        assert response.status_code == status.HTTP_200_OK
+        assert 'refresh' in response.data
+        assert 'access' in response.data
+        assert 'user' in response.data
+
+    def test_logout_view(self):
+        user = User.objects.create_user(username='testuser', password='password123', email='testuser@example.com', role='student')
+        
+        # Generate refresh token
+        refresh_token = str(RefreshToken.for_user(user))
+        response = self.client.post('/logout/', {'refresh': refresh_token}, format='json')
+        
+        assert response.status_code == status.HTTP_205_RESET_CONTENT
+        assert response.data.get("message") == "Logout successful"
+
+    def test_logout_view_no_token(self):
+        response = self.client.post('/logout/', {}, format='json')
+        
+        assert response.status_code == status.HTTP_400_BAD_REQUEST
+        assert response.data.get("error") == "Refresh token is required"
+
+    def test_dashboard_stats_view(self):
+        admin_user = User.objects.create_superuser(username='adminuser', password='password123', email='admin@example.com')        
+        self.client.force_authenticate(user=admin_user)
+
+        response = self.client.get('/dashboard/stats/', format='json')
+        
+        assert response.status_code == status.HTTP_200_OK
+        assert 'success' in response.data
+        assert response.data.get('success') is True
+        assert 'data' in response.data
+        assert 'total_users' in response.data['data']
+        assert 'total_students' in response.data['data']
+
+    def test_dashboard_stats_view_no_permission(self):
+        normal_user = User.objects.create_user(username='normaluser', password='password123', email='normal@example.com')
+        self.client.force_authenticate(user=normal_user)
+
+        response = self.client.get('/dashboard/stats/', format='json')
+        
+        assert response.status_code == status.HTTP_403_FORBIDDEN
+        assert 'detail' in response.data
+        assert response.data['detail'] == 'You do not have permission to perform this action.'
+
+@pytest.mark.django_db
+class TestInstructorViewSet:
+
+    def test_instructor_viewset(self):
+        user = User.objects.create_user(username='instructoruser', password='password123', email='instructor@example.com', role='instructor')
+        instructor = Instructor.objects.get(user=user)
+
+        client = APIClient()
+        response = client.get('/instructors/', format='json')
+
+        assert response.status_code == status.HTTP_200_OK
+        assert len(response.data) > 0
+        assert 'id' in response.data[0]
+        assert 'name' in response.data[0]
+        assert response.data[0]['name'] == user.username
path: server/backend/users/tests/test_views.py
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**INSTRUCTOR VIEWSET TEST NEEDS OPTIMIZATION.**

TEST VALIDATES INSTRUCTOR ENDPOINT FUNCTIONALITY, BUT VARIABLE 'instructor' IS CREATED AND NOT REFERENCED. ADDITIONALLY, NOT USING AUTHENTICATION FOR INSTRUCTOR USER.

```diff
-    def test_instructor_viewset(self):
-        user = User.objects.create_user(username='instructoruser', password='password123', email='instructor@example.com', role='instructor')
-        instructor = Instructor.objects.get(user=user)
+    def test_instructor_viewset(self) -> None:
+        user = User.objects.create_user(username='instructoruser', password='password123', email='instructor@example.com', role='instructor')
+        # No need to store instructor reference if not used

+        client = APIClient()
+        # Optionally authenticate as the instructor
+        # client.force_authenticate(user=user)
         client = APIClient()
```

ADDITIONALLY, CLIENT IS INSTANTIATED TWICE. OPTIMIZE CODE:

```diff
-        client = APIClient()
-        response = client.get('/instructors/', format='json')
+        client = APIClient()
+        # Optional: Authenticate with the instructor
+        # client.force_authenticate(user=user)
+        response = client.get('/instructors/', format='json')
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    def test_instructor_viewset(self) -> None:
        user = User.objects.create_user(username='instructoruser', password='password123', email='instructor@example.com', role='instructor')
        # No need to store instructor reference if not used

        client = APIClient()
        # Optional: Authenticate with the instructor
        # client.force_authenticate(user=user)
        response = client.get('/instructors/', format='json')

        assert response.status_code == status.HTTP_200_OK
        assert len(response.data) > 0
        assert 'id' in response.data[0]
        assert 'name' in response.data[0]
        assert response.data[0]['name'] == user.username
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

81-81: Missing return type annotation for public function `test_instructor_viewset`

Add return type annotation: `None`

(ANN201)

---

82-82: Possible hardcoded password assigned to argument: "password"

(S106)

---

83-83: Local variable `instructor` is assigned to but never used

Remove assignment to unused variable `instructor`

(F841)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T16:58:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,94 @@
+import pytest
+from rest_framework.test import APIClient
+from rest_framework_simplejwt.tokens import RefreshToken
+from rest_framework import status
+from users.models import Instructor, User
+
path: server/backend/users/tests/test_views.py
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**OPTIMIZE IMPORTS DIRECTIVE.**

UNUSED IMPORT DETECTED: 'Instructor' IMPORTED BUT NOT REFERENCED IN CODE.

```diff
-from users.models import Instructor, User
+from users.models import User
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
import pytest
from rest_framework.test import APIClient
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework import status
from users.models import User
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

5-5: `users.models.Instructor` imported but unused

Remove unused import: `users.models.Instructor`

(F401)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-12T17:22:20Z
author_association: CONTRIBUTOR

PR comments:
user: login: gitguardian[bot]
created_at: 2025-04-12T16:52:30Z
body: #### ⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.



<details>
<summary>🔎 Detected hardcoded secret in your pull request</summary>
<br>

| GitGuardian id | GitGuardian status | Secret                         | Commit           | Filename        |                      |
| -------------- | ------------------ | ------------------------------ | ---------------- | --------------- | -------------------- |
| [15278966](https://dashboard.gitguardian.com/workspace/635407/incidents/15278966?occurrence=183599448) | Triggered | Generic Password | eb9e594ce453d5f9c5c4719f7bf348b5bf07cc6d | .github/workflows/sonarqube.yml | [View secret](https://github.com/Learnathon-By-Geeky-Solutions/seagull/commit/eb9e594ce453d5f9c5c4719f7bf348b5bf07cc6d#diff-a5e11e7bedd328777f4897395a711a4492d413b70b12479230470ec608d96792R34) |



</details>

<details>
<summary>🛠 Guidelines to remediate hardcoded secrets</summary>
<br>

1. Understand the implications of revoking this secret by investigating where it is used in your code.
2. Replace and store your secret safely. [Learn here](https://blog.gitguardian.com/secrets-api-management?utm_source=product&amp;utm_medium=GitHub_checks&amp;utm_campaign=check_run_comment) the best practices.
3. Revoke and [rotate this secret](https://docs.gitguardian.com/secrets-detection/secrets-detection-engine/detectors/generics/generic_password#revoke-the-secret?utm_source=product&amp;utm_medium=GitHub_checks&amp;utm_campaign=check_run_comment).
4. If possible, [rewrite git history](https://blog.gitguardian.com/rewriting-git-history-cheatsheet?utm_source=product&amp;utm_medium=GitHub_checks&amp;utm_campaign=check_run_comment). Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider

-   following these [best practices](https://blog.gitguardian.com/secrets-api-management/?utm_source=product&amp;utm_medium=GitHub_checks&amp;utm_campaign=check_run_comment) for managing and storing secrets including API keys and other credentials
-   install [secret detection on pre-commit](https://docs.gitguardian.com/ggshield-docs/integrations/git-hooks/pre-commit?utm_source=product&amp;utm_medium=GitHub_checks&amp;utm_campaign=check_run_comment) to catch secret before it leaves your machine and ease remediation.


</details>

---

<sup>🦉 [GitGuardian](https://dashboard.gitguardian.com/auth/login/?utm_medium=checkruns&amp;utm_source=github&amp;utm_campaign=cr1) detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.<br/></sup>
author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-04-12T16:52:34Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: rate limited by coderabbit.ai -->

> [!WARNING]
> ## Rate limit exceeded
> 
> @ImamIfti056 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait **3 minutes and 45 seconds** before requesting another review.
> 
> <details>
> <summary>⌛ How to resolve this issue?</summary>
> 
> After the wait time has elapsed, a review can be triggered using the `@coderabbitai review` command as a PR comment. Alternatively, push new commits to this PR.
> 
> We recommend that you space out your commits to avoid hitting the rate limit.
> 
> </details>
> 
> 
> <details>
> <summary>🚦 How do rate limits work?</summary>
> 
> CodeRabbit enforces hourly rate limits for each developer per organization.
> 
> Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.
> 
> Please see our [FAQ](https://docs.coderabbit.ai/faq) for further information.
> 
> </details>
> 
> <details>
> <summary>📥 Commits</summary>
> 
> Reviewing files that changed from the base of the PR and between d10ce12345d38035b28f00ba18bf1e235f45cc0d and 6ede01baa5aaf064be065d824b11ab8e0e043fec.
> 
> </details>
> 
> <details>
> <summary>📒 Files selected for processing (2)</summary>
> 
> * `server/backend/backend/settings.py` (1 hunks)
> * `server/backend/users/tests/test_views.py` (1 hunks)
> 
> </details>

<!-- end of auto-generated comment: rate limited by coderabbit.ai -->
<!-- walkthrough_start -->

<details>
<summary>📝 Walkthrough</summary>

## Summary by CodeRabbit

- **New Features**
	- Enhanced course management now supports course images and enforces a refined rating system.
	- A new forum has been introduced, allowing users to create posts, comment, tag topics, and vote.
	- A comprehensive quiz module is now available with categorized questions, dynamic quiz attempts, and public access to quiz data.
	- User registration and authentication have been improved with clearer role distinctions and updated token handling.
## Summary by CodeRabbit

- **New Features**
  - Enhanced course functionality with support for course images and validated ratings for a richer learning experience.
  - Launched an interactive forum that enables posting, commenting, tagging, and voting.
  - Introduced comprehensive quiz features with structured questions, options, and attempts now accessible without restrictions.
  - Upgraded user management with distinct roles for instructors and students for a more tailored experience.

## Walkthrough
The pull request updates the CI configuration and project settings, adds new migration files to define initial schemas and schema modifications for courses, forum, quiz, and user apps, and restructures test suites across multiple modules. The workflow file has been updated (Python 3.10, dependency path, and .env file creation), and conditional logic has been added to Django’s database configuration. Additionally, minor file cleanups (e.g., .gitignore adjustments, removal of unused imports, blank line removal) and SonarCloud configuration updates were applied.

## Changes

| File(s) | Change Summary |
|---------|----------------|
| `.github/workflows/sonarqube.yml`, `sonar-project.properties` | Updated CI workflow: Python version bumped from 3.8 to 3.10, dependency install path changed, new step to create a `.env` file added; SonarCloud configuration now targets `server/backend` and specific test paths. |
| `server/.gitignore` | Commented out Django migration ignore patterns, so migration files are now tracked by Git. |
| `server/backend/backend/settings.py` | Introduced conditional logic to switch database configuration based on CI environment variables (GITHUB_WORKFLOW/CI) versus default config. |
| `server/backend/.../migrations/*.py` (courses: `0001_initial.py`, `0002_course_image_alter_rating_rating.py`; forum: `0001_initial.py`; quiz: `0001_initial.py`; users: `0001_initial.py`) | Added initial migrations for courses, forum, quiz, and users; introduced new models and fields (e.g., course image, rating validators) and defined relationships and constraints. |
| `server/backend/coverage.xml` | Updated the timestamp attribute within the `<coverage>` element. |
| `server/backend/forum/{serializers.py,views.py}` | Removed an unused import (`cache_tree_children`) in serializers and eliminated extra blank lines in the views file. |
| `server/backend/forum/tests/test_models.py` | Added a new test suite to validate the creation and relationships of forum models (Tag, Post, Comment, Vote). |
| `server/backend/quiz/{tests/test_models.py, tests/test_views.py}` | Introduced tests for quiz models and API endpoints to validate quiz functionality and responses. |
| `server/backend/quiz/views.py` | Added `permission_classes = [AllowAny]` to `QuizAPIView`, allowing public access to quiz data retrieval. |
| `server/backend/users/{tests/test_models.py, tests/test_serializers_auth.py, tests/test_serializers_token_dashboard.py, tests/test_serializers_user.py, tests/test_views.py}` | Consolidated and extended test suites for user models, serializers (registration, login, token, dashboard, user), and views with improved verification of user creation and authentication flows. |
| `server/backend/users/tests/test_serializers.py` | Deleted the file containing tests for `InstructorSerializer`. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant App as Application
    participant Env as Environment Variables
    participant Config as Default Config Retrieval

    App->>Env: Check for CI variables (GITHUB_WORKFLOW, CI)
    alt CI Environment Detected
        Env-->>App: Provide POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD
        App->>App: Configure DATABASES using CI parameters
    else No CI Detected
        App->>Config: Retrieve DB values via config()
        Config-->>App: Return DB_NAME, DB_USER, etc.
    end
```

```mermaid
sequenceDiagram
    participant Runner as CI Runner
    participant Workflow as SonarQube Workflow
    participant EnvFile as .env Generator
    participant TestRunner as Test Engine

    Runner->>Workflow: Trigger CI pipeline
    Workflow->>Runner: Set Python version to 3.10 & install dependencies (./server/requirements.txt)
    Workflow->>EnvFile: Create .env file with secrets
    EnvFile-->>Workflow: .env file created successfully
    Workflow->>TestRunner: Execute tests in 'server/backend'
```

## Poem
> Beep boop! Code circuits ignite,  
> Python upgraded, shining so bright.  
> Migrations born in orderly arrays,  
> Tests run smoothly through all our days.  
> Configs align like gears in a plot—  
> Our code sings, linking every dot! 🤖✨

</details>

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- DwQgtGAEAqAWCWBnSTIEMB26CuAXA9mAOYCmGJATmriQCaQDG+Ats2bgFyQAOFk+AIwBWJBrngA3EsgEBPRvlqU0AgfFwA6NPEgQAfACgjoCEejqANiS4AFWTUS4DAOWzMBlLgFYAbAYCqiJ6QAJLMaMwhAGbiAAy+RvrG4FBk9PhROATEZMo09Eys7Fy8/MKi4lIy8kxKVKrqWjpJJlBwqKiYWYSk5FT5CkUYnJBUAO6QiG7hFPJyCnUqapraumCGyaYGGkTqsNgCAPRj+BQA1lEW+GOIh4j4GGgUAI4HJBqyzBYcBgBE/wYAMSAyAAQRCOT61Dok2mT3kGUYsEwpEQZlgJCRKOkKCwuAxkAABjs9gdjqcLlcbncHk9Xh4Pl9CZAovArLiGBZsEpINhuLRocgCJB8Zi7PiHpApBREPAHgAaEUE7jUWAs064xxoCwWeAYIiQJTcNJkBjwaSKzD0UXoWi0dRyrCItCTGjcEX4RgUEjQ9BEjRkCTM1lWDSQADKxrNrIY2osskVNvFsEl0tlkuRMhIZCx+phUQoLCJAGYNAAOZnCwmlgCMsWJMAJeq1OuojsG4Qw9CiGqNJowZpxmcgHhzzEU8FZMOFiCjk/kNpV+P9d0o0sO3te8G9bGGiA0uAAHrhmc2aGh0plF96JHLsMhCZvsNuSLvcPujyew6C7Q7aTqEz9cgJkcEh3WHUcsD1XBC1obAGGnT0GG9X0XWJQNgzZEhFTGBAGDVbh8G4bALEFKUnjvZBA23B433Iih4BUKxkHvPUDSCZCSHfMM4ExQpO2tT0KGwPFpHfSBh21e4R2zLA+QFAZhT1TluUxRdVSJIIKHXAQ0AYM40mZDwe29JU1LEjsrUVMgpgY/UlWoMyFGlNBSFGMDTlwFBkF6PIYSYChvTEeMNCMYEwQsGh+kdIVPRtJROSeNsHmQRESEPQiKAGDViIEXUGEgdgHWkRJIGcT1tSi5KMFipyEtI6KUv4TJ0sy7K+Fy/LCuGYrkDGShMXCHk9SVVB7SiKJQv+X5Ei2LT1xJcQiAwU5rD+AFwvBSE/PoKZWHhZrc1RdE+ORPNkBGm1iV2JaVu9TD2T1CR8AsKQnPHcb4FjcRJURdTcCimreSCQSUGW1bIAAESEFFPWYeAiEarAQ2kHiCV1cghWRLzeBIW98HveNJjnKdQYRu7MTjSB4cR6qWSw5AAApCQAKhZw4aaR24WcJABKdAuw9XkMHJiGroAfXFvV1EljRuFkB7MTGPYRsweROeq5B7SCghZkgZmQDZjmEa5w5Jel3BZflvmJLQN7II7dgYQJzRG1QNhMCxxz/sB5AnkxFaJhgn0BjQZB+PYP3BeVnVIBWry0AmionI8ZF8b4P6CWuh1wfu+nQzBP33KmSLLVjjWfuRhnIBjiw489K48z4DwwYp2hLUFiWpZFy3xblhX86Vtk66kz0W+UrklHoS6CRgvSzjYkd5AAcUaIwwpBUFIryGKhfi0QGs1w7Ws852OoOLqivEEqDCgcr0G3rm94Jeqksr1KWoy0/0nPvKvu68Q18+oDWpheTEM8xqTkmutGat85prkoIcXS+k0hIPnqgoIAM2L7nlj8aaQJN4Ql8v0GEe0ZgIkyPhbEaIDC8SOjiaCsF4J8QePaSu2pIBXF2AVYU8VqAqDDiwjArIiDYCRriJy0NYaTC4uIfUyAUZhkjKIScX04yAUnE5aihYMB0QkBRJiOJCTLxCNAAAEv4AAQuLAA6gAeQAEoAGkABiAAZOxNjmQakJAAYRCMyf2MjcCKj1Pab6i98TewJGgbg3B8p01QMJDAIt7Kq0gKvXAZiDhgjELvbRtF2CJizpDUE0BQSWNBOGAAouGZkTBhEIzEYk5AH1VGIWBpiApuj2D0UYnlHEJlDT8N0kEBQKSKjtiULgbQFh9xuwugOSeOJMFyINHwmZoyA4RGwp0igHd6AqkQIgE4FBQb6K5IMwszAiQ2DseGaAy8HG1PFpDSxhJFSEjuQ8p5Lz/A1IcR8gW9Avn3Mec88M4sbBVPDPYhxkMgXelnJMqQ8YcJ7D9EoKIaASJeURAAcgcJbWgAh8Xqj4BsgRYzHhsGBZAfFhFHCI2kGSoZAh8DLnvJQOlRyTmnFoGjTEKZHDeWCULfFVxYwWGFbgMlVonJtXFV4AALMWAATPisM0QzJjO6XogxAy/amXjsE4pmJTgIz1BwhSVKhEiOaZXYGkSs4NJEcGESeTJQ7m0EDD1Z1SDt3cjBc0t57IXOwIMnxbzxbOFBAAWWqUCwk0b/nVMBZ86N0LwywscQijN1izFgqBfK5N1i7kOOgA2do4d/WU0fljIRMEXosipIdG0sT4lqMdVs9IeICR6t6SNXCX01TqFFck1JRAy5UkXkM8aUQBrDGGZswR4z7VPxGkoVFRE9GUCmMgfx3Vbw6LfPuDeEUqrv2fpiV+T80pfyymfHgF9/5X3NDQu+FVH5H14S/A+b9d73rak+zqr6epAJriAoa4C+2QImlNAEcCwBGHmog5BBkuyHCYGIoItwK4xUOLEIjNZu4Om1P3PBG1CHbRIbtOEetERUPOkYUEccSATHw5KFGtssw5k4tCbsGobRSP1BVOJCTK7WS1H/RACB7I2gtoxOuNqtmTHwq+F0QyrrYZlNIQJcS0buxNok4YTCEIKIJnwD6JA5lcD8ZZoISbfEOZIM54Ykck3VIwIWHUb5i2C0JA4ts+oGwnSJM5nDJBmTWbrhPVSCjzQWFoBZvghJ4C0CTYAqwSalCIGQvAbglccsOsdDlqBX1cUK0+VMcoYgk3RXkf5+gmnVrk0gAZeQhJ+P5HFnIZkGMF7ybilnM8wkxCnGi4oGzEirpcplPp7ggqiTzoq5FAeU4kuioaY4KgeoOnIujP/fC+AvrSC4L8DwuwJkUF+IqX40FKBsHtNCW7dLfgXn0QOOgvxQq0KzhF3TbmaB7km0oOu22kDiURBt5L5KiTpaTQ04HltcCyGNJlywUXPliIsE1okNBjziyR+wIFytlxdYeMj8WqP0ecPgPDRSSEUyneQFMfC6BkC/FvEofAr3fj8iiHzgnuBXvyve1lL6VgfuhATnMz0cXct+hMiQNrHWiQ6ccw5Ly3pSIOGveFlzoObO/boYSLzPmLB+eplNuuc99Ks9wKpJdZALenprhimHKX4cZc+S7l6VhaDi2oHj5XquSCyC9xTyLgSAuOCdyeM1nDqBRTpxgQb6zhuYkJHHpQwwjexb7VnubiAFtaq8mQEy5m/QiXgK8IRO2fV4sL4MNQjxHWIij7pmPIKc8k5N1nILaz8+MFiY7pFezRjBaIH1D3iXYdDLSz7okDWiAh9a8tdr4fI8a6i3Swkc2gU2l1yngbkTM8T5i4aEgrJyDTywKs7BZeH7SQr6cKvLoa91/GQ36C/Bm+FFbzpg7x327yJAP1+zsRch1GKSM1pkdUwRYndBtB7BEgUkdA4R23gjH0xCGU7FckXh30QDNW3HGWRyIO6ld0jgOUnzWRn3xBnkpnEy7UdF+3PS3kvV3l/RvX/TvU/mAx/mfT/gKjfRvigDjRt1tCnjswB01xGmzwQQoDQRQUw0IONjgIIyI1iBI0U3I2tlvkgHELB0kLoGkJcyBw8wkXkO0jQ3QRUJczw2M3fkI2I1I3EF0IVn0MMOmwvCkKJHN39ytzkNQ0UPQ1QVUM4xqmcK0NcKU37kJE8IkJ8JMKJEHzYlPCwCsJ0lsNoCw3sLUNNk0O0J7liL0PwSQxQwUKUIwxyPCMcI0KIzVSJxcylnCFICD0fnFhX06KnwoxgQITBCIVyFo1hH2gY0oVrRoVY2AmpjqK4ywh4xkhzEYUUGYUEwpQJBEyIDE07QiQVGJhURjHURFCeFIDWSck71w1LwWRmPUMlB8KLmmJhzjh2RBXp1cl3y4PV0N2tzBxwjwlHS1mvz22ayLkJH8AcTcRcTn0rGxhHywBbisBiBHFIjT34D4EwSFgwBIgsG/F/HYQAkTwiIfiigbUKkPEh0XkfCn0whszv3OJX3z0VCYyuwNHUEBJvz/DxAqluXwFlEqBIHDHCB1BCHc1IAoChNpKrQQEQIUnzDn3rgmAVxxAuXS2oFOC9nL2ETf0phmJFmYDcHIkuUOhrDpRdHCHJP1JuXDXNUyC8GshqjEWdUchXyNXrznmBPd3oNg2QCoDzFYP6PYJ3iak+NvSPiA2/jRMEMvnA3fVKglM2ySNoDszeNIHSJ+OmwuN3yCMqNCLsMizwxtxwQ8KgHjOa0fmSKpKHwkUv0rLSMsOCKqLCLyJiyLPiLKIgGQwMAbNzJqPwBclIA0EPC+Eoxmk2kGKhAGDIQOkYwmLC3nUmBTBuD9DaUOMdU+MJHEDYC1GYG4ECQBgYgEDwCHi9POOACYH7JID0GZBs1fF6Uziz3POUAHKHNx0HiWwtSuw4U3LEgiHdGtMOkJBrAAHZlVVUgKfAgKaw1UfAILmQIJZJeR+QBMhZAKQLQLIKywawfAABOLwIChsLeOuDlDEPgZPA8o88ghXSktMUrT5DGaQMAFUpfQkeixAMAR870Zi+isAEhJNAQX09TNijiugPigSjENipi0SzAfCHi6EPHCnHcqwck1HRFDTEaP1bEAVa45km9fAHEE1ROBdMQJyPs5QWOTAsQMRc1PgYnJde8yAAADTjTcUHhklkFYScnkmQu/O3MW3XgDO/SvRDJ4LDL4IjJyhfWEJjNELKi/Q4ODPP1DKvXDMfQENAyisAXfUg1Mmgxm2lMNCgQQ1gQ7IqOsJCOyMOBMjcHyM1iiKKLIxxNwT6LHJo2QqnLGPoRoWrRuPEW42WLgirwU2KOtRGVXTywxHCDhxa2EhuQ7Qk3bFYnsk2PwCfyxT2wszEXTNsyJGgFciTRsF5ITwNyGCOpLQADUOUoswwwtCRdrV8tqORlko5uhWqBheA3i9Y1d5UP8RYv8aUcC58lsvlDrh8F1qArLnqcVshiFkL3ryFN9AIstdlbKQkRR6cfydyvdut2x5UvKHBLRyUVcN81dPi5sHqtMYk8AUx9lTTQEMBZAwBshOx5AddNYEB3Qyc1Qro7r89+8s9nMTrh8IdHAP4BYXqYa3qGJ4aOsmTKcik0atyZlMbqDPc4cEBlAKB8I1E64LLsC6b3i3xQklluRF5ESE5BYGIiBYAvJrTyDg5MQQhIZqCrBUUn9R5Hr4sld18sASbz8ybL9vrCaw8Fxz9gbHBGTPS1QXR7ag7ibw84cVRvQl0I49xp1rhZ0NRMYBgU7uIbqLqaBh9JIsAoaeghjYapaDoZa/REAhSC8aAxT6ZaS4dnoaBDSI19ZOakQTsq8hk+QW7KZBZaBrgMB+7eYVbvaEbap2VlwroDrw66V/bCyn9X9NacQfra927tt3Sl1hRcYpAl19TIoCt2R+6FFrknJEAdkJ87jJg2J2RGVNAwsiSfyZNxKr9jQuxTQsrJQbRF6jC1rb88rMRlrglaD/TNpArODEqQrkqwrUrIz0qAFepSovCR47QKzEpjkYA9r6ycyKqqrmAaqnDCiYj3D4ixDEj0Gkz1dSIsG56TxcGyrGzMMCGiH6joidDGriyDDKHfCutaGD0WBAiMjuz8HThqqIjbgSHOG4iEijDEy7NMHkB86syRG8HlCcjWHJG6rSGuG2zEMSquz1HqjKrxHCGtIlN4AAAvPdXo/BFqiW0hejChTqsLHS3EZ6V6HEG0HcPsjhZ0KCHczyV0aEOiBfWMdTanb0EgInBAJLJO4Mc+q6HcgGA8V8eJaEGZafDQFJlHVyEvH4kiGDc40RjR0xma1cBibUax2x62N864sHLidsMJLtbx2E5Aj1fE9QFmmzbypCPSOTdZaJpENkWgJOv4kdUBeQNOTEB2LlNYiSc0KgTWvCEamZdyXGIIYYaqUJZAFaThB4Bup8F8Ok+ghLAuOhJFPFK8AkWoXZKi+TAkCx6pmxmUOHW6vaz5AWvzT5eh+SlR1S8IZsYWHSwNFpiJR56Jc1KAnWmCLAqyulKITp9A3UVHNtJ5ygSx15103EGZMQcBzeSBhKuqGBwDOB9qKMsDTKmKsIRVXxqQahwkAsIsXJtJxSzJ/JnJwrPJ6fFAIJrKEfSJ+22J0ZhJxhrIsp1h553UbF2R9s+BJhns8p6qolW4IlcWFsuxqjAY16px0YlxnSyYtjIOCyKYdQIVMORYqCUzFYhCU5gkbjDcsSDVpeuprTMSZ1HA5F/8bp9FrPHm35kGr5oRknagwkAFrauguTP0EBua5gh4JbIlRgARnasSVBgpsaUQU4ZCzuwkAAAXliJRyaeDOA0FoBhlE3FhJSBXBany1xQC8lrlxeUGMs7spVUzgjsnWQ9f1Gur+0xDNdbqVNZzxjMpFAsjYAlGSx+AMBrDDDZnVayaJxQkrjZi4GgDElJOxt+kyDQh5s1BmS+0VGlHnCdK8htH+q21OB1iJjDllGWhhHlSiXPaeeDXsm9E2fYDpnCFwEEq0QygqGdjwGIkfoMDVXnZZnVYfuXZDkdDXZgE3acm3adF3Z5PnrPGkspmOXwDNBzYxTQkCEoFAL3b2qf3U3t3rcXEOvxT9iptOETEx1lvc2GGoLvZw8YkUnyfQFMgChvfkEwV+1LEgAXedZzpg+qng43ZFqQ5XfbGdGOqtwGw6TQj+dprBK0gbBCBtsxanA1KQ5DeGBo72UVAfqY+R2oJ20Xg/aRS/cdSCT0gYAdXeCMGVQg/VbE4/fjEk8Q5tGQ8OhdE8862+ZJyFhdETt6RzvtNsjPYVX9mGHYrifoFZvfnZqvcCgqCJhft1FkzoF+y8Dc+df7vE9XZZnXZ89udk53b9HDcusCWw9w4GE7oI407U7+aY9ZAoHhkhZfcxDmxM8OuoP7rbsph4+vYy/kDvfJly6MB8AK8cGpw5W1HFlPu8+k98+1Ec91zk6vEW7rlPqmrQ4YfmGxvskPvEHiUxH2+ZZuXnUXS8mL1NOOUoDOOfacgIBmTrm9EuAqFJNakA/SDwEKGc4MEgLMosET1Vecm5RtFGX/l8QcX8Ehn4GNCfnlWS5inZqzFwH6hzBtBbKi8dO66ciRYHC6bRdTjtjXsWWByngJYvSDKBmCsSl4LJP4IQciqQaAVKmcHY3HZFSUeMMZYF6k9wHTbTMyJsMlbMcOFVZl+dc1dKIMYVYlZMdYdvHYyLJHIDPHJ2hGPhpnOoTcYmKAZ4ELG5xhHnKehejeh8dfD8aIqvBOGRMwDOFTyuSLCulKdV+l/V5uDiMHkVAO1USlSJmMjFj/WZ6AJuaz3obOvNDGHDC4nqQEbDHvhJ89UeHBwmMVC4X/kqgZ/II1BXPjcZ89EoELD4DOloAxgNA1CRx8xbWuFtjehw8c/S5BL9CRVxXRbg0mkbGhbB9dDGz1vlQp/Tj9YWExG9XUoHFrQFX8ogfitL5Jcj9gdZ/Ct/mjOpZoXKnICKtmk7K99QS3CsbYZSh0Zkaavseo0cbo31cOkNfRFgN6qwkVFv6Xihgra2Kk02Wy7fqGoarLpbUamCatNT9An90ATBXYhgCfz9VmECWN6C2WkLQgtiswJNAAEUI0jgWikSDsSFYcBhITAdY1BAAx0mp1ALIRwoCggao/UCgKFn7bhYUBpwAeJfmHaN0ksXuLoCXV1b0BHaBNS9ozD5BCw6wsQLEFQDEB7ox6mKaQPlnwF7FA6PlJWu6CGTIcgamAj1g8CFqU4fUkNPAKXQnIwg+BTkOvNgJ/rpRUaLoPzooN/KsdY6PteOqfjSTntuSfiJgWgL7am48BRWcmiHAhpi1uB7/IwURDXLmD9YQg4UGqi8BeAxBekEklIJdDsoXoPoKuE3VrZnFNENoYIc03DhjcxAtg0PHHRDrnF1BpgjABHSa7uRdcMIS9kMhvB7pKYDAczLVCyEpQ+aRIIgVYxIE0AUmw+O3GcBYhaR0ApAlJn7C8gn8bG5BTuqrSGRcC9BPA0IE7TUyrRZailJppKC1CO47a6NXyl7lwi48YkwwwrCE3gbypCgF3fIOPW9DB1kAjgjPCOGIrnFfEbggeE+wJBk1BBsoJal/3wBGc/6NmXmL9ihKZ80U5xSgdQJOREcHqQUflFHAhFvNdg+9D/sXiNoqR7Q9kWYdDTLoDAjBCQ/AEkK6BPEVBuQ3AJjHILyoCh9giPKnnTz65CBz4ToYcIYYloShRWS0MAljiWsugLQzPnYIRr64ggVgCQSCi8GlY2hz9B0uPle5UwWy3HTELwCIiUAiYSnZrJ3HaYEhyATQg6KrSCTdZ9sfIRVDaAgHp8yesgOnoGSfhM9D4a/E+PAwipCFOesZORt4SoaKMU2Tw+uswPF5H9MMJ/M/pEWkbDU9GTotBnwwF4sjSs4rSXiY19HaMAxDVWRhQ3kYuiaGd7SACKM0GRjyqZTGMbMX9EuFL+3DVBoL1dGpiOhXQsgV6OMbH96RfoqRvmMDEJieGSY0MSmzBE0DIR2ZRVhVRzG3E8xHDBsYr2KrK8ox1Y6xnLxFrjjLYCvWQFrwcZYi9W+vcYobwMBTFeeSbQdjgSEwEgIBcbaAZGwWIOw4BdrE3o629E5FfRsvdVtOKlKoANxvIcQDKzaZZ5C2YkRJjslOSu95UbAl0BYxxB/QfOnoJir6A6ak8UWvreyq4I9FoDPk4YzQZ8nTFlCYJ9I8sT0LDZti4R+eeZNUgGZ88vIk7FMAUBXYMIaoh7KvPZSRRRh+S+476k9yyh6dRQJBMivAEPJ64dRxIomAJyMBzthOLMEXu83dEkBUBA8VBpJyzjudnhLrMHNFi4gESpQOnLKlKMYFQSB4GHL7HCRkhegQ4MINtrc2JHPFaU8qXLLIMrihRwOPEviQvlgllCmxNmUSVnnVYmDK4kkmzNJKnZIhRA/Q+tmhCsnpEtQakzoPVw47aSMUvnPSZBMEmei6Ur3NkuO2PCioHOTnDibrGm5Cc2YFknxAhOZAiTSu/ffHM625HOTXy+ExQN1FshtNHIXQQkJlIPaYdqRyndoVgO8Gxh4SfEIifQB0ksJ0uxlYXHSl44VBSRITTYaFFc7mSLIlk5CYyKyk247JeU+bifyDyMjCprk2SZD186VcUO1XMsZNJqlHs1024LruslhJjo+pwUCboFLIgVD1OkIktAJKEl45XuNoPLBDFOEsBzh7YDYfeFlHoBGhiUnpgFFp4GB8uo0kVAvnQm0CppYOGaU63m5zYg87YigEtOpgySSpQEvXIpKulUD4Zvk0iXxC6AtxdRgaW4Q231wnSvIdI4gZNKQkaDEJdKAUf91wFyCaZw6dkO7XI40jQpXUkkdIAdzg0z0DAyHkEhmAGR2pIUrOAW3sBiRi25wMtt8KrYCBmQCUbNrrFC46gm+KmVdA525mGhCeh08yCLTNFEtl++8VfmS3X62jN+VLZBvoR54mt+eKbBRimKwYi8OhYvTMcw3PE1jLx8vV1twxtlIy3JDswkFilwlE4JJMWRmAKKiC8xKxXY7MZ7M3aTjCprZa2bz2KnNZkxQc6/CHMcmOhCpEcmzFHJjkq9Rxp/L2fN2vEpyOMyM9OXw2DnqsCp4cyOdHLdlKsLxCcq8T7PIZlRU51c4sctizkOT6RC07oYVjzlNyi5I4n0fHInEdywcycu+D3IDkZy65zrWGZ7FoFjyC5zczscXKnljiy5U4zuX0XKJGNY50Y6ee+ETm+9NezVG/vOLv6LjXGK441rhNhDms4cho+kZAJ2J0x4KSxG1gNRhAMFXKEvLMefP3ntzCu8fVsoZlZzPhW6eANkDU1JKEgXx89AsO+IpBRSNi3wyABCmgAsgqAbAD8UZ0JCggbAIQXxLqBC671KAJkK0hRAJhChEOP9bcfSJ4q9MBg5CkIN1FoCERoI8yOhJDyVIztuJbMW6cwMgBx9eeifXAPBzOryTypPXEfMpKlDx8g0YiIGC6DVREY0xTiQaZ9L2HF1Gh3M6bmZLZgdCwQFCqRfH1W7iQHpbC6xlYp4Xegx8WiyADotEF2J9FH0qONaF0mcy44bgDwBnEyA5ymoMwyAAiL4zPDQoqUlmD+HoCWLpFYweDr4gxAUdFJUwFHmTWamwgTFxyJFgBE0moQX5EA76BFNmDWQAOeSdER4q0KQBfEbUgxX4v06BKKlQk/ScU1t6zgUoznEaWzESWQArJNi9jPBy8xlS9OP4vkJQFyVcC7Q4Axqe2GFCWDnhx7apmgUeZT9pAhEGqAOxmSbDsFmIcJaJGPChQgZbMSGDeVbrJLbFOUkXqSUabt5UO5S54R/2mU5KtI7XfaYvG0W6LvFLS7BVC2Li7KxkW5S+m5DYE2hTk9AX4B0otTSAfsBgWbjxP8BIUblX8lJfIsUVTLYQny7lHkrxoD0ySFJeyK8uUlMkMlNIyJZ4r0WArXhvXdFf5DeWXtgFFEvpaFCAoQdwwBwBnMMsxV3Kyua3J5ryqQDpgNpO4+GdUGrxfLSpOsv0LSoBW+KjlCgQJU9NMhPtduJvdlXsraECzTIutKyqDChoYgeoFSvZH7D2bZgp4dKVGWpH7Q1LsogPFgJGj4A+h2c3C5tuIJMkL9CWS/WqMbKtGmybRFLRBiIQ/SxViSBffXElWDUPpQ1HPcNdlUGhgI8qvfffifLPGHBfR18rVqOTvkGCH505JccxgYHuMjxa9F+Sj3hjHJ2wAvCmlnjLEUKUlRkCbr+HsioLHsYq3OUoxxAABeSAAAG1CK1wagbIAAC6N41pBOFXI41VZS5ESFcwYhCjvpTQ2kU2pCAtrFQHsSdF5PprGd3IRAJ4NXy1nOgqaRUPcb4qZJcCClpJZeNUgIVpye+yAT1b7yWx71KIg0m0sUOQnNr4+Cs9LPXC8hB8ogE3fddWu7WShe1kw/4iKDRza0iYWKHFJFFqjHMu2WQU1eIGgGwL6EUat5u2lvWkEG+Da5xfaSYiLxEGmsrBp8QgE2og0DEPGBwmNFgTUcBs/1TGtJZNQUqCa+0eGtKg2Au1taqDfbIzkQahNGAInAI37VDqR1YwMdZOokQC9yZnQv9exgnlgKS5hwXNYOIP6lVd5ORYvLWIv4DiZxt8nVu/3aoGtZyf2Z/nTD6oAL4BSoYZtMSQFEhKBSaEUrrQmxhtwwjuXPF+FykYzh8mbe9oA2FDmC0gJLbFN3z+F1x5R5vegPMGWoojTgmUOtqrTZxR0HwvKGFUmloaWxc+NMtLIgHFjZLZlGnbHFpH+pJoOu83arXRTDiWx6tRINSrjk+RIBStMyCaEmg62xDJAWOZbNCHFhCATst+erEkPkpqB8AmnWXC/mkx/8cQzNRmoQGZqVC2aBWKNkSERgExuABTEtLDLE3iqS8adZWPZCGSHb2weBUgHRHlTbahBbHBrjFDaGEhPNcLcbHQIepsDphPiXLOTGqigF98IsGiipWoJjosuSAN+i6AeAkBltYAaHWtpS4FZI6oIlrjFgJ7obXu7q0dCROH4ahUAyo0LsLA3q9dBhDnAmMMCUT042QTwEEVdF83x5eh4gzyZmSaKrD8gAOnfOLD9w6gRKZcaSETPXLuaHq+IQsNgCtp+hodsO+HejxSjs0xRuY4JIgScgajuZB0dWWMnGoaYP54lQaIWRRGTwCCKUbeuJEMnX5kNNtbUFgNB3AaSYCkpsIGK/UT91dA7dTJNQ6nw5Axe5YNCxIHZcQUK0AYSFdVyl9hP6A4LKmkPtXRJbNjqHXN/WLqm85QVmBXdd3OImrS8vq+nhaOgYmyuN5LEDImuioRqixgcgXkLp3mTyDNWkBwr2LrH9j4xehRMc6JbGpjXtuOj7eXo02YZDNsY+sfXsLG8MMGKbenf5vU3uzDg3e3MbXvqpuEgx8rQ/lWK71V7Jxarb2XPLzXa95hlmh/tZroSGsTe8W9LBbygQeNreOpULcH0wDa4xIw/eVfZSTaxhcMcOWLYsnOKzybMrZepnQg/VMLk2d7MAD2iH7wtTIndIIInWAkWRoN7zEXpQNQYA6RererAqcFgMHiEK22F6KqQGDQRuSnwogOyAF7/UQU0BrSGLz7Y2IVY3pF+Yau9DFJhmSbNOcgBmZWtcWZmCsu5yImdb484sA/J8jYNaTu4XmhGdwb3zqtYZO2DVtXKlL2TRO7B3vMMC4Oo7e5q0irrByq4/i/NvSPrh7TREGg72z3XeCRrmxGd5RKPCDAyo5z3AGupCdQwfRtxGcmJPuwRWJOkN8HRsiBwQwobcmyh4YDUImEodakqGNpXQVw+9oPUntQNnrA9m3qM6/blodMXGkDr3S+t7DFFPtoks5LqJE80My2KIZgjiGp29SKlaSUs7vswINnbZs8plWUB31N4T9ZD17qDDkOgfN9gaGs7SBbOjoUHTjrcMlLHUjBh2PSzoBlxDmyOnOnQDh14BJ+cOWQ+e03ZLZpikPRBY+IKpJwk693KrTslaU5aYRgwcLqDFt7jgGWuEskaqPjpKlJC6RkeLRKCqASdOC4V9r6HsrO70AMQblLkvWkQEYWiePfV90FEJwFyk4ZwWMGPVFxxwBq4OBEHooqiCg+AcSv1twm/K4khYHCfUcoAcKqh9AfHuqEc44G/8MnAI7avkl7jEQZNGiRYaCkYnEi+5ZiSkYz3mif02eoNbnrNk8at+VsqAFQtTFJ0XiJYp2WJBgM25R9SrQzQfKTn+9AASYSOzkARBygK7I71j7hTkC8uUfLEK9zA5K8+brqI4P+b5DlAfORYELmKaU2t1Pk8QcLKCmKqCpmeavvf2Ni40qp5eQPOcNDbgjusHUxQD1MGmRoSm6UxQFlNqMz5qCS05fLf1zJbT9p2uY6ZhlaROtCMtOR6e3lYBvTJpmU2aZbkWml9IpiuXPr00V7x9GZxU5bGlY1MZQ6+ucYWr17Fqn5dCJ5XKXZBZHStmLF5rUwHhI4fUMIGvDMek4kaXtXR97YnyqaPiPt8zE3rGygHVRYBxtG1ZpngDHgEWXWdgy6Ymz1sMS921+apIQgT8ezAhiOidx0MT53dPmb9fdl7O6wNAvwXKUC1EggzvWGAHg860XMIyizrzIFHav8W+6Bz1jKPmLS3NvbdY2MzDmyK0gvc1R75rFhb34Sqq+O7kb7iuuvAKiJcf46PkSAQPvbQCMUh7eSb2RkcqV04EC0SGq3sCMT1AdTG+b2SXt7Kmh14cCszmhb/te+GvMDvWxz5WkxFjEKRb6m9Kuwvyqkw4c3MoW/zuJNhCixBFjowj5ofxcCqfPgX1mrZj0jaD+4rqOsjhkBECb9hy4/Qox2gOMc7NeQhkVfGvh4w2VACJm7ObQE9iFg2QEWDi0C02b4C7YQY1XM6hsuqjVJAoS5oxUMJHkvdAJzl1unRuikDh2J8gJUs1hrgMRf+WeAizDjY3RrLRAGJkyGvz28bC93POKrFYZPxWgY3GpK6yYgy0CU1SgDNYYyzVBmV983KSzKCDxU1SzBa3XlvoN6lrVxts4DfAs3F8AyaFVhg5a0PEObjxwCx1uq06tVX8QcRHDXeKhWIduzDiQSZDkoD9mwLH2ktG4nwBXZ5rtl91KBJ9ZAIgaPpta4OYKYC9ZLQMM7sfT1l4Tq5enO1SnBIBpxE9frDXY2f2vCw6gfSH/Q0iEutCbq6rb0LsFAiPnHrxZ5aSVLZlFH4ITQopYeshziJ5OZNTukxWMvmG9DRPX+qTp4sUVQEv7LmvbpA5ACgVyirY2cjS5QXMwbFt2upbEtKLquw+kLuufAThxmlndJDd31trvNOdZwqwOzr3yc7udAeYkF9edY/XZr/1j8683Fic1+Dv5pc0+q8PU6KAvh8rtst+tzxHU8otdTMOtYCH0dMXIagIeiPSA/tdnQWAxcSNottRpkThjU2hOcyQo/N+boLb+sNmRblAbuExU6ITb/ZK0hWzIidsUAjOOqsZCsutbw2yah5/WPigvBdd8U8Qq49rYxY+3RUJqeG1ReUUdpCw71X0OXyL6q7IVPke+c9vVaFbHbC15bhsqBv0AKbpJQrabxw5azIlwd2VWzNjs2XBzGiigIzwdWUTEIGGVpSHaSGIV1hYN7mRDdRksEjAbiRrbTqcPzdC7FVl2xspg7+alMBTJ9X4YOZXZvbC1ozvpcXiIgnoRlziIvakha2ieaEJy7qDQIPBXLFfU8D6W0AOWPLcFhLWRavryoCb9AIekBoxumX916UUlQaDmwfGweieeY4hzIBnQNzoUi8odBJPnqzVcRi2jNYbyOpmNW199MfcOmORp6aoJO4LD3vn21MZARhTi1TtEQqmNAImPpem7rxF+6Vlfoyayt560qBe7ftzzXEQGRNze3k44EoF7XizGbf0/przN7pl9icoayarlZK959AZxfcI5FNDWCAGGKtmHFgDspj1NV8zffIrMdVH+z8kByKjvF/zrWTaQBQsyJQEF7wBAG5Ao5zCdXaaAoWTKo8JsbCKSDAEdvHeAWjmf5JkwLSL18QWOWA0AfABhl4fYtk+qYo60KCd50GLrdmdzv4+YALdFHggGZHqHFgqhtwRd9a8IdE7xPEnZATotfiRSwBMng5+gSKVmmWxsTljvJxJuSc+o0n2gYWwteLQT5UABM6gldD8eOAAnQTsgHYgEApOMANgRpyE47GLI/JbhRrhinrsErOKRUKSImwgOFGUASFiq6KiYqKhXFDGqoE5Co2xQMM1BRWYrjHQrKrj9bKp0WE9yB9bdIfeQPikPNypBY+KDrWVuEhaR8UloUyJ+yXQvTrbPTGC8osMOjDvdNJhgfWYucJPrHEmr7kU5KeA2PbJUmWz4ZqBESi4JJwWI4yLgwvpAXNXp3JBxOBZCnOLwJxhg0AmQ3TM2w0hgafHhZ4nJLsgNNYLA4vRnH2g80S9kwegDn9GkNDS72ecuyAhzrNsc+cFySGIoGhVGUY3O/OdYA0+yvc6SGPP6Azzkra88MOEW+ZaR/EpPf9ZiRIYyjxx7QF83UBEALLsJ1g2/EIuQU6rexyo/wDHrOtxruF8+ZMtqhdRkTz0A90ZtQI7uowJIa0tXvJp9Xdrs5Ea/fCmun8dqoowDdebI6wuczm/P5Bcy43wk7CPWKSee70SCQr5waS45YuY3/2nd8u5btdUegPuFqpo/Hkoonn1SstfMmZyoJ0pxhqMIwKDyoDQFldbD6TpZdMjWXGA8Tje7ZYUQ3nIL43PG3VCDfHqc32A1xwO5bsJSSERMVW6YsWQ42bUMVrPXQ8yui1ErTD5Kyw8rmvyBeJelNr47pd4uWX/DzSAvsr2yOCzTrvdDU6UcOPg3AqBvd3OauWu+5mcq8Dk+6eQu8X4sOp6k/SdNPbL8Z8XsLzEhdPLH9LjABe/NNlNSroj6Nw+6hdPvbXajt937OidLyIzP7jU7k7Q/YvZM9790+PMsKQfHA0Hnp8E5Q/zY0ziH/M1afKt0eStaHm1wa8bHYf2HqY496mJF56vn3x60Nya7o8IeTGSHwa6x8fcceX3XHzt+ddw8Vl1TxKCd2cgdfvhSP4Hijye91dqfDXBy0Tz7fE+BmmPwZ51vI4A+yfMPHhbM6fMEeSeLP0ngB1f21ZbQLNzjbfcuKauvy7xKJ9Eqx5QP/zjHqxE8fMXrNDWXPCsMa61fvFIKJh5xNBQwwwXEKsFX4ycyslHZtvD3giWqFdZAkZ9qmaLCCTw7E975+LpwcN5ABuq7XWPZrnIds2BbIF/c6dU7jE64kQd6zohsT6JPdiKGvbT5r8810hGCARA+LGXD0crVk1abj3IC5m+buW3cbP7P9ijcoB2G0b+NLQ5SQRyVbKAzWwkK1vG3ZZPkU2+Si85mVvOiOv2cxTzHVYPnSPrMe5flRXsDfo3Q3jIhV4+2jeKgZHVF5Ub4AzeDzfd3e9W5ppbOeXW7Mbpxe0PIXQf/53aem7omUc47YF9+xBahUBKdY3uJNPt5iNt4cB+2hIzKG6Z82DA8S+s/d9ns1qcDFL3r60n6/CrZmN1u2HdYgmffTXNcU1bs/OkDBpvrSbtb2wm/J4yBtUKS9+c+/w+NznNF2P996nLP0ZZ9jA46CvtLmkkd9wY4jYlzI3+0blqzNncbQ6CL6zR+lIYd+wDLbvq86M7PbwfpYlHMyR7+u2e8M/7FWcUr8Z+R02+0fMyJ/AfsVwugU36BPWE1ywBkD5Ac2S9t9SDtGXWtgFjN8j4W8xvUAidjZdQRBvx/v5adsh10l19Izjk7xSPOH52QA7DvX07515GAUVWjOmdks2Fkh7dvI9yi2x5Q/uHLgcHd+eG3RtJkgjLW/UDkaSW+cbmNQKgLZoP8yBoaYQMumqJj3Xf0nN3LPHd+zz3dsn33h7jh4Pv4/JmKA8HhjxJ7M9lXCzznrSPJ6rlKfGWKnt01p/I9yFavPty96AvlN7/kPt/t08f8/dqnIzlsSn3R+0/X/N/2/uU0KaP+Uns/5ReXcn7JPq7/vh6f+oPqR4asAvkQBumP/hkQ3+C1nf4lWQAU54gBR/lh6LyskpAEhy3Xj7Zz259nb5oASAamzcOWkP/4COuZo54seWAVUY6amate5COMoCI7qs2mqZrX8GjuWb1WJasdC6OCnm/Kt0hjswa2sQCs3gDWUChrxxELrlobeMTvNBp2YIvKCBU0KSntoBY8BqD4pKsipIYUBuAKoH4g6gfV6kEhvkEBQEr8vQb1s+Xg6w3mRXi4wwOooHA6OoaQPwp7gQNN9aIOUUMtz/qn7mn628StuIhLuxyLH5a+GDn8Z8+k3lCZtKWPv7Z02rdrfj66JtFsqm8bRmpLEmpOp3B4uQAh4HOshdr7xl2oru0iV2K1iNAoOmfL6zHcsnHUrouBQFSoRGcQVNSEaaPOy64u3drkHT2K1i7A+Bamp+6r2XCDL7BB5BN24xcLoPDbEebQTmBd8KGhIjTKt6hDYDBExnEETM7IFdAF2XQXgA9BYwOLArQNToUH+BBII0EeW+zFC6iovvhr4KWZxAkIXg7kI5ItKHQap5Ce6nhsIlaBQX0Fe24PoxoO8wyE8G7QByrm5TUxdLA5YayFBHbqUsqroZhBErmkGj+rPPTJ0abKjsocqgWta76eGnq8Hx8OwfgBpOgmuKqFB9frip3QQpBPhBQKuDs7TUagOgz34ByoYpc+wvikxn4q6lrJ8IvwYNJ8ypuFoECGOgUny/65rhl5FwOBqsETs1cnebzc93tfJJ8cgRXZ7qGGherIUD5upKfBb0C6DZc1zJEZuGZIjHYpBjQcqpmGA/t+qXBcpLSQv6PSqCo3o/CIA5tu4PB27NWOQtKCvWn0mHCUSotI4GYaJfHSgKhdGkqHag6Dun4cWuyjD7XyMkIwaWshofQCoE3KLdyrGBDo8AMQvJDP7XGc/qFTMmOVpbJc8B7vfpr+QvLp6OAhgbADGBO/qZ63uzHpbCcBYAUIEQBDplAEFOSts7a+85Afwwb+uYWoHQKJnjI5sBIpmWEZhwoaf52Y5/vkHx8DYZR4GBLYRrxthN7h2F3uXYQvIfulYXh4hyiwaWGDhV/hkTDheYQWEAB6ZsWHme83NOEr+OHngFVhC4RsFLh7GFiE1OQ4TmEjhRga2GFh7Yfv77+WwfPL7hPYYeHzhqIb8HohWwZeFNh14fmG3hm4Yx7bhj4XuHceIqHOHKeH/uh4GuX4b7znhF2g8A/hXDn+EbhNAZ3oThD4VfLQKr/pmG8eGcsOGfe3IQwyARu/sBFYRMgTgGzh4ZpBHVh4odApcQ8ZroB6AsVOQAQeV4YRHx8ugXeEYR7AdIF+8TAcVa0gFAGADyiY3poDGGehmdhma7npo58BVZrczG8wCtnhCRIkYWBiRcsGnaSRBTPZqeMb0ESoBquko0iiIvVBqDhgQkVQoEwKotqAR4SSIJLHqi8PcBiIUrp3AWQ2sBUAIqylppBCRGgI5Gr0BTBJFZQ0zN1YIUxfB0hnG9/j2TYyPoJeCQAQgBY5XuTDIfgkUJAPMBfc0sCkG+RG5m5Hva/HDc7jmYIHiTCWiNFnD3AsYWkwi0zIAFFosoga1BWg+2CTBzADwhFEVUhBADroBe6KpQZMZoCkHf6n0jgYkQTwAAgdUzURoySGYKjOol8VEBgDgO9qgOw3O4EkhZLgsAAML5gGoJlED01oBZCYA1kbKB8yNDhu6BqW7sfDxqqYRlTL+ykWVHrRBTESqMspUU8A+RlmOZh9qzLMxw5E8oA2TMgEphdH3RV0c9E6INPIcDvR17l3LfRFAOVHvgzIDdF2Yd0WDGqsfameKfRXkWVFwxWaoQRAx0jhhH6MQ4hsCtAvCv5xzC7/DnRcA4wFo5zANQFNj1AywE0BrAhgLjH8QMsOlglaNRuxh0ADrgKxJABgLjFqoaqBeBQUQFGWAqo2FGWCxAAgMWBoAaADWDFgsQCQBAU2FMqjxAPoGWA+A1+GqgkoyqLQA+AXgCaQtAKQJAA1gyqDWBQUeFAIAMAxYLQAMAHgKrFeAqgNbEWxyqF4BoAXgAwCxAZYFECxAxYJrFoAPgMqhRAAgDTGcxusSoBqoxYAwAJQxYNhRqoqqAIBAUaAMWDAUsQOrGqAtAALGgUPMQwBlg0FFoQ1gTsf7G4xtAHWAIQUFMWD2xtAMWDCxxYNbFqorsURi6QNYGWACAUQDWAkAwcV4BRA9sY0KxA9ABzF0xQjAzHJYBTr7ysxkWvoBAAA -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=Learnathon-By-Geeky-Solutions/seagull&utm_content=56):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: Warhammer4000
created_at: 2025-04-12T17:24:13Z
body: ![](https://res.cloudinary.com/snyk/image/upload/r-d/scm-platform/snyk-pull-requests/pr-banner-default.svg)

### :tada: **Snyk checks have passed. No issues have been found so far.**

:white_check_mark: **security/snyk** check is complete. No issues have been found. [(View Details)](https://app.snyk.io/org/warhammer4000/pr-checks/61a5cb26-58f2-428a-82b8-2571d4ac3ff2?refs=fd1965781eaa3626832872cae4217e975ae555cb&source=prChecksComment)



author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-04-12T17:24:54Z
body: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=Learnathon-By-Geeky-Solutions_seagull&pullRequest=56) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [15 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=Learnathon-By-Geeky-Solutions_seagull&pullRequest=56&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=Learnathon-By-Geeky-Solutions_seagull&pullRequest=56)


author_association: NONE