diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f894a5a..3ac5b22 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -10,6 +10,6 @@ The goal of this file is to help the larger community settle on some best practi
- When making a pull request, reference the issue number it addresses.
- Use clear and succinct commit messages.
- Keep your pull request focused on one issue.
-- Describe the goal of the changes.
+- Describe the goal of the changes in the project.
- Mention specific developers for review.
- Make code readable and include code comments.
diff --git a/README.md b/README.md
index 4db9136..fc02fcf 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,15 @@ The Learning Management System (LMS) is a web application that facilitates onlin
## Technologies Used
+
+
+
+
+
+
+
+
+
- **Django**: A high-level Python web framework for backend development.
- **React.js**: A JavaScript library for building interactive user interfaces.
- **React Router**: For client-side routing.
diff --git a/backend/api/serializer.py b/backend/api/serializer.py
index c6698ab..52f198f 100644
--- a/backend/api/serializer.py
+++ b/backend/api/serializer.py
@@ -1,8 +1,8 @@
-from django.contrib.auth.password_validation import validate_password
-
from rest_framework import serializers
+from django.contrib.auth.password_validation import validate_password
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
+from api import models as api_models
from userauths.models import Profile, User
@@ -97,3 +97,403 @@ class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = "__all__"
+
+
+class CategorySerializer(serializers.ModelSerializer):
+ """
+ Serializes the Category model.
+
+ Args:
+ serializers (type): The serializer class for the Category model.
+ """
+
+ class Meta:
+ fields = ["id", "title", "image", "slug", "course_count"]
+ model = api_models.Category
+
+
+class TeacherSerializer(serializers.ModelSerializer):
+ """
+ Serializes the Teacher model.
+
+ Args:
+ serializers (type): The serializer class for the Teacher model.
+ """
+
+ class Meta:
+ fields = [
+ "user",
+ "image",
+ "full_name",
+ "bio",
+ "about",
+ "country",
+ "youtube",
+ "github",
+ "twitter",
+ "linkedin",
+ "courses",
+ "review",
+ "students",
+ ]
+ model = api_models.Teacher
+
+
+class VariantItemSerializer(serializers.ModelSerializer):
+ """
+ Serializes the VariantItem model.
+
+ Args:
+ serializers (type): The serializer class for the VariantItem model.
+ """
+
+ class Meta:
+ fields = "__all__"
+ model = api_models.VariantItem
+
+ def __init__(self, *args, **kwargs):
+ super(VariantItemSerializer, self).__init__(*args, **kwargs)
+ request = self.context.get("request")
+ if request and request.method == "POST":
+ self.Meta.depth = 0
+ else:
+ self.Meta.depth = 3
+
+
+class VariantSerializer(serializers.ModelSerializer):
+ """
+ Serializes the Variant model.
+
+ Args:
+ serializers (type): The serializer class for the Variant model.
+ """
+
+ variant_items = VariantItemSerializer(many=True)
+ items = VariantItemSerializer(many=True)
+
+ class Meta:
+ fields = "__all__"
+ model = api_models.Variant
+
+ def __init__(self, *args, **kwargs):
+ super(VariantSerializer, self).__init__(*args, **kwargs)
+ request = self.context.get("request")
+ if request and request.method == "POST":
+ self.Meta.depth = 0
+ else:
+ self.Meta.depth = 3
+
+
+class QuestionAnswerMessageSerializer(serializers.ModelSerializer):
+ """
+ Serializes the QuestionAnswerMessage model.
+
+ Args:
+ serializers (type): The serializer class for the QuestionAnswerMessage model.
+ """
+
+ profile = ProfileSerializer(many=False)
+
+ class Meta:
+ fields = "__all__"
+ model = api_models.QuestionAnswerMessage
+
+
+class QuestionAnswerSerializer(serializers.ModelSerializer):
+ """
+ Serializes the QuestionAnswer model.
+
+ Args:
+ serializers (type): The serializer class for the QuestionAnswer model.
+ """
+
+ messages = QuestionAnswerMessageSerializer(many=True)
+ profile = ProfileSerializer(many=False)
+
+ class Meta:
+ fields = "__all__"
+ model = api_models.QuestionAnswer
+
+
+class CartSerializer(serializers.ModelSerializer):
+ """
+ Serializes the Cart model.
+
+ Args:
+ serializers (type): The serializer class for the Cart model.
+ """
+
+ class Meta:
+ fields = "__all__"
+ model = api_models.Cart
+
+ def __init__(self, *args, **kwargs):
+ super(CartSerializer, self).__init__(*args, **kwargs)
+ request = self.context.get("request")
+ if request and request.method == "POST":
+ self.Meta.depth = 0
+ else:
+ self.Meta.depth = 3
+
+
+class CartOrderItemSerializer(serializers.ModelSerializer):
+ """
+ Serializes the CartOrderItem model.
+
+ Args:
+ serializers (type): The serializer class for the CartOrderItem model.
+ """
+
+ class Meta:
+ fields = "__all__"
+ model = api_models.CartOrderItem
+
+ def __init__(self, *args, **kwargs):
+ super(CartOrderItemSerializer, self).__init__(*args, **kwargs)
+ request = self.context.get("request")
+ if request and request.method == "POST":
+ self.Meta.depth = 0
+ else:
+ self.Meta.depth = 3
+
+
+class CartOrderSerializer(serializers.ModelSerializer):
+ """
+ Serializes the CartOrder model.
+
+ Args:
+ serializers (type): The serializer class for the CartOrder model.
+ """
+
+ order_items = CartOrderItemSerializer(many=True)
+
+ class Meta:
+ fields = "__all__"
+ model = api_models.CartOrder
+
+ def __init__(self, *args, **kwargs):
+ super(CartOrderSerializer, self).__init__(*args, **kwargs)
+ request = self.context.get("request")
+ if request and request.method == "POST":
+ self.Meta.depth = 0
+ else:
+ self.Meta.depth = 3
+
+
+class CertificateSerializer(serializers.ModelSerializer):
+ """
+ Serializes the Certificate model.
+
+ Args:
+ serializers (type): The serializer class for the Certificate model.
+ """
+
+ class Meta:
+ fields = "__all__"
+ model = api_models.Certificate
+
+
+class CompletedLessonSerializer(serializers.ModelSerializer):
+ """
+ Serializes the CompletedLesson model.
+
+ Args:
+ serializers (type): The serializer class for the CompletedLesson model.
+ """
+
+ class Meta:
+ fields = "__all__"
+ model = api_models.CompletedLesson
+
+ def __init__(self, *args, **kwargs):
+ super(CompletedLessonSerializer, self).__init__(*args, **kwargs)
+ request = self.context.get("request")
+ if request and request.method == "POST":
+ self.Meta.depth = 0
+ else:
+ self.Meta.depth = 3
+
+
+class NoteSerializer(serializers.ModelSerializer):
+ """
+ Serializes the Note model.
+
+ Args:
+ serializers (type): The serializer class for the Note model.
+ """
+
+ class Meta:
+ fields = "__all__"
+ model = api_models.Note
+
+
+class ReviewSerializer(serializers.ModelSerializer):
+ """
+ Serializes the Review model.
+
+ Args:
+ serializers (type): The serializer class for the Review model.
+ """
+
+ profile = ProfileSerializer(many=False)
+
+ class Meta:
+ fields = "__all__"
+ model = api_models.Review
+
+ def __init__(self, *args, **kwargs):
+ super(ReviewSerializer, self).__init__(*args, **kwargs)
+ request = self.context.get("request")
+ if request and request.method == "POST":
+ self.Meta.depth = 0
+ else:
+ self.Meta.depth = 3
+
+
+class NotificationSerializer(serializers.ModelSerializer):
+ """
+ Serializes the Notification model.
+
+ Args:
+ serializers (type): The serializer class for the Notification model.
+ """
+
+ class Meta:
+ fields = "__all__"
+ model = api_models.Notification
+
+
+class CouponSerializer(serializers.ModelSerializer):
+ """
+ Serializes the Coupon model.
+
+ Args:
+ serializers (type): The serializer class for the Coupon model.
+ """
+
+ class Meta:
+ fields = "__all__"
+ model = api_models.Coupon
+
+
+class WishlistSerializer(serializers.ModelSerializer):
+ """
+ Serializes the Wishlist model.
+
+ Args:
+ serializers (type): The serializer class for the Wishlist model.
+ """
+
+ class Meta:
+ fields = "__all__"
+ model = api_models.Wishlist
+
+ def __init__(self, *args, **kwargs):
+ super(WishlistSerializer, self).__init__(*args, **kwargs)
+ request = self.context.get("request")
+ if request and request.method == "POST":
+ self.Meta.depth = 0
+ else:
+ self.Meta.depth = 3
+
+
+class CountrySerializer(serializers.ModelSerializer):
+ """
+ Serializes the Country model.
+
+ Args:
+ serializers (type): The serializer class for the Country model.
+ """
+
+ class Meta:
+ fields = "__all__"
+ model = api_models.Country
+
+
+class EnrolledCourseSerializer(serializers.ModelSerializer):
+ """
+ Serializes the EnrolledCourse model.
+
+ Args:
+ serializers (type): The serializer class for the EnrolledCourse model.
+ """
+
+ lectures = VariantItemSerializer(many=True, read_only=True)
+ completed_lesson = CompletedLessonSerializer(many=True, read_only=True)
+ curriculum = VariantSerializer(many=True, read_only=True)
+ note = NoteSerializer(many=True, read_only=True)
+ question_answer = QuestionAnswerSerializer(many=True, read_only=True)
+ review = ReviewSerializer(many=False, read_only=True)
+
+ class Meta:
+ fields = "__all__"
+ model = api_models.EnrolledCourse
+
+ def __init__(self, *args, **kwargs):
+ super(EnrolledCourseSerializer, self).__init__(*args, **kwargs)
+ request = self.context.get("request")
+ if request and request.method == "POST":
+ self.Meta.depth = 0
+ else:
+ self.Meta.depth = 3
+
+
+class CourseSerializer(serializers.ModelSerializer):
+ """
+ Serializes the Course model.
+
+ Args:
+ serializers (type): The serializer class for the Course model.
+ """
+
+ students = EnrolledCourseSerializer(
+ many=True,
+ required=False,
+ read_only=True,
+ )
+ curriculum = VariantSerializer(
+ many=True,
+ required=False,
+ read_only=True,
+ )
+ lectures = VariantItemSerializer(
+ many=True,
+ required=False,
+ read_only=True,
+ )
+ reviews = ReviewSerializer(many=True, read_only=True, required=False)
+
+ class Meta:
+ fields = [
+ "id",
+ "category",
+ "teacher",
+ "file",
+ "image",
+ "title",
+ "description",
+ "price",
+ "language",
+ "level",
+ "platform_status",
+ "teacher_course_status",
+ "featured",
+ "course_id",
+ "slug",
+ "date",
+ "students",
+ "curriculum",
+ "lectures",
+ "average_rating",
+ "rating_count",
+ "reviews",
+ ]
+ model = api_models.Course
+
+ def __init__(self, *args, **kwargs):
+ super(CourseSerializer, self).__init__(*args, **kwargs)
+ request = self.context.get("request")
+ if request and request.method == "POST":
+ self.Meta.depth = 0
+ else:
+ self.Meta.depth = 3
diff --git a/backend/api/urls.py b/backend/api/urls.py
index 872c994..bb81723 100644
--- a/backend/api/urls.py
+++ b/backend/api/urls.py
@@ -14,4 +14,15 @@
api_views.PasswordResetEmailVerifyAPIView.as_view(),
),
path("user/password-change/", api_views.PasswordChangeAPIView.as_view()),
+ # Code Endpoints
+ path("course/category/", api_views.CategoryListAPIView.as_view()),
+ path("course/course-list/", api_views.CourseListAPIView.as_view()),
+ path("course/course-detail//", api_views.CourseDetailAPIView.as_view()),
+ path("course/cart/", api_views.CartAPIView.as_view()),
+ path("course/cart-list//", api_views.CartListAPIView.as_view()),
+ path(
+ "course/cart-item-delete///",
+ api_views.CartItemDeleteAPIView.as_view(),
+ ),
+ path("cart/stats//", api_views.CartStatsAPIView.as_view()),
]
diff --git a/backend/api/views.py b/backend/api/views.py
index 8a9463e..9dfb5cc 100644
--- a/backend/api/views.py
+++ b/backend/api/views.py
@@ -1,10 +1,7 @@
+from django.conf import settings
from django.shortcuts import render
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
-from django.conf import settings
-
-from api import serializer as api_serializer
-from userauths.models import User, Profile
from rest_framework import generics, status
from rest_framework.response import Response
@@ -13,6 +10,11 @@
from rest_framework_simplejwt.views import TokenObtainPairView
from random import randint
+from decimal import Decimal
+
+from api import models as api_models
+from userauths.models import User, Profile
+from api import serializer as api_serializer
class MyTokenObtainPairView(TokenObtainPairView):
@@ -131,3 +133,183 @@ def create(self, request, *args, **kwargs):
return Response(
{"message": "User Does Not Exists"}, status=status.HTTP_404_NOT_FOUND
)
+
+
+class CategoryListAPIView(generics.ListAPIView):
+ """
+ API view for listing categories.
+
+ Args:
+ generics (type): The base class for generic views.
+ """
+
+ queryset = api_models.Category.objects.filter(active=True)
+ serializer_class = api_serializer.CategorySerializer
+ permission_classes = [AllowAny]
+
+
+class CourseListAPIView(generics.ListAPIView):
+ """
+ API view for listing published courses.
+
+ Args:
+ generics (type): The base class for generic views.
+ """
+
+ queryset = api_models.Course.objects.filter(
+ platform_status="Published", teacher_course_status="Published"
+ )
+ serializer_class = api_serializer.CourseSerializer
+ permission_classes = [AllowAny]
+
+
+class CourseDetailAPIView(generics.RetrieveAPIView):
+ """
+ API view for retrieving details of a published course.
+
+ Args:
+ generics (type): The base class for generic views.
+
+ Returns:
+ type: The serialized course details.
+ """
+
+ serializer_class = api_serializer.CourseSerializer
+ permission_classes = [AllowAny]
+ queryset = api_models.Course.objects.filter(
+ platform_status="Published", teacher_course_status="Published"
+ )
+
+ def get_object(self):
+ slug = self.kwargs["slug"]
+ course = api_models.Course.objects.get(
+ slug=slug, platform_status="Published", teacher_course_status="Published"
+ )
+ return course
+
+
+class CartAPIView(generics.CreateAPIView):
+ """
+ API view for managing shopping carts.
+
+ Args:
+ generics (Type[generics.CreateAPIView]): The base class for creating a new cart.
+
+ Returns:
+ Response: A response indicating the success or failure of cart creation/update.
+ """
+
+ queryset = api_models.Cart.objects.all()
+ serializer_class = api_serializer.CartSerializer
+ permission_classes = [AllowAny]
+
+ def create(self, request, *args, **kwargs):
+ course_id = request.data["course_id"]
+ user_id = request.data["user_id"]
+ price = request.data["price"]
+ country_name = request.data["country_name"]
+ cart_id = request.data["cart_id"]
+
+ course = api_models.Course.objects.filter(id=course_id).first()
+ user = User.objects.get(id=user_id) if user_id != "undefined" else None
+
+ try:
+ country_object = api_models.Country.objects.get(name=country_name)
+ country = country_object.name
+ tax_rate = country_object.tax_rate / 100
+ except api_models.Country.DoesNotExist:
+ country = "United Kingdom"
+ tax_rate = 0
+
+ cart = api_models.Cart.objects.filter(cart_id=cart_id, course=course).first()
+
+ if cart:
+ cart.course = course
+ cart.user = user
+ cart.price = price
+ cart.tax_fee = Decimal(price) * Decimal(tax_rate)
+ cart.country = country
+ cart.cart_id = cart_id
+ cart.total = Decimal(cart.price) + Decimal(cart.tax_fee)
+ cart.save()
+
+ return Response(
+ {"message": "Cart Updated Successfully"}, status=status.HTTP_200_OK
+ )
+
+ else:
+ cart = api_models.Cart()
+
+ cart.course = course
+ cart.user = user
+ cart.price = price
+ cart.tax_fee = Decimal(price) * Decimal(tax_rate)
+ cart.country = country
+ cart.cart_id = cart_id
+ cart.total = Decimal(cart.price) + Decimal(cart.tax_fee)
+ cart.save()
+
+ return Response(
+ {"message": "Cart Created Successfully"}, status=status.HTTP_201_CREATED
+ )
+
+
+class CartListAPIView(generics.ListAPIView):
+ serializer_class = api_serializer.CartSerializer
+ permission_classes = [AllowAny]
+
+ def get_queryset(self):
+ cart_id = self.kwargs["cart_id"]
+ queryset = api_models.Cart.objects.filter(cart_id=cart_id)
+
+ return queryset
+
+
+class CartItemDeleteAPIView(generics.DestroyAPIView):
+ serializer_class = api_serializer.CartSerializer
+ permission_classes = [AllowAny]
+
+ def get_object(self):
+ cart_id = self.kwargs["cart_id"]
+ item_id = self.kwargs["item_id"]
+
+ return api_models.Cart.objects.filter(cart_id=cart_id, id=item_id).first()
+
+
+class CartStatsAPIView(generics.RetrieveAPIView):
+ serializer_class = api_serializer.CartSerializer
+ permission_classes = [AllowAny]
+ lookup_field = "cart_id"
+
+ def get_queryset(self):
+ cart_id = self.kwargs["cart_id"]
+ return api_models.Cart.objects.filter(cart_id=cart_id)
+
+ def retrieve_cart_statistics(self, request, *args, **kwargs):
+ queryset = self.get_queryset()
+
+ total_price = 0.00
+ total_tax = 0.00
+ total_total = 0.00
+
+ for cart_item in queryset:
+ total_price += float(self.calculate_price(cart_item))
+ total_tax += float(self.calculate_tax(cart_item))
+ total_total += round(float(self.calculate_total(cart_item)), 2)
+
+ data = {
+ "price": total_price,
+ "tax": total_tax,
+ "total": total_total,
+ }
+
+ return Response(data)
+
+ def calculate_price(self, cart_item: api_models.Cart) -> float:
+ return cart_item.price
+
+ def calculate_tax(self, cart_item: api_models.Cart) -> float:
+ return cart_item.tax_fee
+
+ def calculate_total(self, cart_item: api_models.Cart) -> float:
+ return cart_item.total
diff --git a/frontend/index.html b/frontend/index.html
index 7aea37e..e30edcf 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -20,6 +20,8 @@
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css"
/>
+
+
LMS
diff --git a/frontend/src/index.css b/frontend/src/index.css
new file mode 100644
index 0000000..432b87d
--- /dev/null
+++ b/frontend/src/index.css
@@ -0,0 +1,4 @@
+:root,
+.navbar {
+ background-color: #dc3545 !important;
+}
diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx
index f77517b..dcab280 100644
--- a/frontend/src/main.jsx
+++ b/frontend/src/main.jsx
@@ -3,6 +3,7 @@ import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App.jsx";
+import "./index.css";
/**
* Entry point for the React application.
diff --git a/frontend/src/views/partials/BaseFooter.jsx b/frontend/src/views/partials/BaseFooter.jsx
index 8d4e448..16d43b2 100644
--- a/frontend/src/views/partials/BaseFooter.jsx
+++ b/frontend/src/views/partials/BaseFooter.jsx
@@ -2,7 +2,7 @@ import React from "react";
function BaseFooter() {
return (
-