Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions .environment

This file was deleted.

50 changes: 50 additions & 0 deletions .github/workflows/django.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Django CI

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
build:
runs-on: ubuntu-latest
strategy:
max-parallel: 4
matrix:
python-version: [3.7, 3.8, 3.9]

steps:
# Checkout repo
- uses: actions/checkout@v4

# Setup Python
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}

# Cache pip dependencies
- name: Cache pip
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-

# Install dependencies
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt

# Run database migrations
- name: Run Migrations
run: |
python manage.py migrate --noinput

# Run tests
- name: Run Tests
run: |
python manage.py test
Binary file modified README.md
Binary file not shown.
6 changes: 5 additions & 1 deletion apps/accounts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class Meta:
personal_email = models.EmailField(unique=True,help_text="Use Your Personal Email Address")
department = models.ForeignKey(Department,on_delete=models.SET_NULL,null=True,related_name='students')
branch = models.ForeignKey(Branch,on_delete=models.SET_NULL,null=True,related_name='students')
skills = models.ManyToManyField("category_skills.Skills" , blank=True , related_name="users")
year = models.CharField(max_length=20,choices=YEAR_CHOICES)
bio = models.TextField(max_length=250,blank=True,help_text="Tell Others About Yourself")
current_password = models.CharField(max_length = 128)
Expand All @@ -44,4 +45,7 @@ def set_password(self,raw_passoword):

def check_password(self,raw_password):
from django.contrib.auth.handlers import check_password
return check_password(raw_password,self.current_password)
return check_password(raw_password,self.current_password)

def __str__(self):
return f"{self.username} ({self.first_name} {self.last_name})"
2 changes: 1 addition & 1 deletion apps/accounts/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
path('dashboard/', views.dashboard, name='dashboard'),
path('profile/', views.profile, name='profile'),
path('update-profile/', views.update_profile, name='update_profile'),
path('change-password/', views.change_password, name='change_password'),
path('change-password/', views.change_password, name='change_password'),
]
16 changes: 12 additions & 4 deletions apps/accounts/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from .models import User, University, Department, Branch
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
from apps.category_skills.models import SkillsCategory, Skills
from apps.category_skills.models import SkillsCategory, Skills, UserSkills

def register(request):
if request.method == "POST":
Expand Down Expand Up @@ -152,9 +152,9 @@ def login_view(request):
request.session["user_id"] = user.id
return JsonResponse({"success": True})
else:
errors["password"] = "Invalid password."
errors["password"] = "Invalid Username or Password."
except User.DoesNotExist:
errors["username"] = "No such user found with this username or email."
errors["username"] = "Invalid Username or Password"

return JsonResponse({"success": False, "errors": errors})

Expand All @@ -163,7 +163,6 @@ def login_view(request):
def logout_view(request):
# Clear only your custom session keys
request.session.flush() # clears entire session safely

messages.success(request, "You have been logged out successfully.")
return redirect("core:home")

Expand Down Expand Up @@ -209,13 +208,22 @@ def profile(request):
departments = Department.objects.all()
branches = Branch.objects.all()

# Get user's skills
user_skills = UserSkills.objects.filter(user=user).select_related('skill__category')
user_skill_ids = [us.skill.id for us in user_skills]

# Get available skills (skills not added by user)
available_skills = Skills.objects.exclude(id__in=user_skill_ids).select_related('category')

context = {
'custom_user': user,
'universities': universities,
'departments': departments,
'branches': branches,
'gender_choices': User.GENDER_CHOICES,
'year_choices': User.YEAR_CHOICES,
'user_skills': user_skills,
'available_skills': available_skills,
}

return render(request, 'accounts/profile.html', context)
Expand Down
Empty file added apps/api/__init__.py
Empty file.
3 changes: 3 additions & 0 deletions apps/api/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
6 changes: 6 additions & 0 deletions apps/api/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class ApiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.api'
3 changes: 3 additions & 0 deletions apps/api/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.db import models

# Create your models here.
3 changes: 3 additions & 0 deletions apps/api/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
10 changes: 10 additions & 0 deletions apps/api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.urls import path
from .views import HomeAPICoursesView , HomeAPIInstructorView , BlogAPIView

app_name = 'api'

urlpatterns = [
path("api/courses/", HomeAPICoursesView.as_view(), name="home-courses-api"),
path("api/instructors/" , HomeAPIInstructorView.as_view(), name="home-instructors-api" ),
path("api/blogs/" , BlogAPIView.as_view() , name="blog-api"),
]
102 changes: 102 additions & 0 deletions apps/api/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import AllowAny
from apps.accounts.models import User
from apps.category_skills.models import Skills, UserSkills
from apps.category_skills.serializers import SkillSerializer , InstructorSerializer
from apps.blog.serializers import BlogSerializer
from rest_framework.pagination import PageNumberPagination
from apps.blog.models import Blog
import random

# API to fetch the courses in HomePage
class HomeAPICoursesView(APIView):
permission_classes = [AllowAny]

def get(self, request):
user_id = request.session.get("user_id")

# Get all user_skills, optionally excluding current user
user_skills_qs = UserSkills.objects.all()
if user_id:
user_skills_qs = user_skills_qs.exclude(user_id=user_id)

# Get unique skill IDs
skill_ids = list(user_skills_qs.values_list('skill_id', flat=True).distinct())
random.shuffle(skill_ids)
skill_ids = skill_ids[:6]

# For each skill, pick a random user who has it
skill_to_user = {}
for skill_id in skill_ids:
users_for_skill = list(user_skills_qs.filter(skill_id=skill_id).values_list('user_id', flat=True))
skill_to_user[skill_id] = random.choice(users_for_skill)

# Fetch actual Skills objects
skills = Skills.objects.filter(id__in=skill_ids)

# Serialize with random user context
flattened_skills = []
for skill in skills:
user_for_context = User.objects.get(id=skill_to_user[skill.id])
skill_data = SkillSerializer(skill, context={"user": user_for_context}).data
flattened_skills.append(skill_data)

return Response({"skills": flattened_skills})

# API to display the instructors in homepage
class HomeAPIInstructorView(APIView):
permission_classes = [AllowAny]

def get(self, request):
user_id = request.session.get("user_id")

# Get all users who have added skills
users_with_skills = User.objects.filter(user_skills__isnull=False).distinct().prefetch_related('user_skills__skill__category')

if user_id:
users_with_skills = users_with_skills.exclude(id=user_id)

# Shuffle and pick 8 random instructors
users_list = list(users_with_skills)
random.shuffle(users_list)
selected_users = users_list[:8]

# Prepare instructor data
instructors_data = []
for user in selected_users:
user_skills = [us.skill for us in user.user_skills.all()]
instructors_data.append({
'user': user,
'skills': user_skills[:3],
'skills_count': len(user_skills)
})

serializer = InstructorSerializer(instructors_data, many=True)
return Response({"instructors_data": serializer.data})


# Custom pagination for infinite scroll
class InfiniteScrollPagination(PageNumberPagination):
page_size = 6 # how many blogs to load per request
page_size_query_param = "page_size" # allow client to override ?page_size=10
max_page_size = 50

# API View to fetch the blogs
class BlogAPIView(APIView):
permission_classes = [AllowAny]

def get(self, request, *args, **kwargs):
custom_user = request.session.get("user_id")
blogs = Blog.objects.filter(is_published=True).select_related("author", "category").prefetch_related("images")

if custom_user:
blogs = blogs.exclude(author=custom_user)

paginator = InfiniteScrollPagination()
result_page = paginator.paginate_queryset(blogs, request, view=self)

serializer = BlogSerializer(result_page, many=True, context={"request": request})
return paginator.get_paginated_response(serializer.data)


Empty file added apps/blog/__init__.py
Empty file.
24 changes: 24 additions & 0 deletions apps/blog/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from django.contrib import admin
from .models import Blog, BlogSection , BlogImages

@admin.register(Blog)
class BlogAdmin(admin.ModelAdmin):
list_display = ("title", "author", "created_at", "updated_at", "is_published")
search_fields = ("title", "author__first_name", "author__last_name")
readonly_fields = ("created_at", "updated_at")
verbose_name_plural = "Blogs"

@admin.register(BlogSection)
class BlogSectionAdmin(admin.ModelAdmin):
list_display = ("title", "blog", "order", "content" , "images")
search_fields = ("title", "blog__title")
readonly_fields = ("created_at", "updated_at")
verbose_name_plural = "Blogs_Section"

@admin.register(BlogImages)
class BlogImagesAdmin(admin.ModelAdmin):
list_display = ('blog', 'image', 'base', 'thumbnail', 'small', 'created_at')
list_filter = ('base', 'thumbnail', 'small', 'created_at')
search_fields = ('blog__title',)
readonly_fields = ('created_at', 'updated_at')
verbose_name_plural = "Blogs_Image"
6 changes: 6 additions & 0 deletions apps/blog/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class BlogConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.blog'
63 changes: 63 additions & 0 deletions apps/blog/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from django.db import models
from apps.accounts.models import User
from apps.category_skills.models import SkillsCategory
import os
from ckeditor.fields import RichTextField
from django.utils.text import slugify

def blog_images(instance,filename):
return os.path.join('blog_images/',instance.author.username,filename)

class Blog(models.Model):

class Meta:
db_table = "blogs"
ordering = ['-created_at']

author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="blogs")
title = models.CharField(max_length=255)
category = models.ForeignKey(SkillsCategory,on_delete=models.SET_NULL,related_name="blogs" , null=True , default="Business")
intro = RichTextField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
is_published = models.BooleanField(default=True)

def __str__(self):
return self.title

def blog_section_images(instance , filename):
safe_title = slugify(instance.blog.title)
return os.path.join('blog_section/',safe_title , filename)

class BlogSection(models.Model):

class Meta:
db_table = 'blogs_section'
ordering = ['-created_at']

blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name="sections")
title = models.CharField(max_length=255)
content = RichTextField(blank=True, null=True)
images = models.ImageField(upload_to=blog_section_images)
order = models.PositiveIntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

def __str__(self):
return f"{self.title} ({self.blog.title})"

class BlogImages(models.Model):
class Meta:
db_table = 'blogs_images'
ordering = ['-created_at']

blog = models.ForeignKey(Blog , on_delete=models.CASCADE, related_name="images")
image = models.ImageField(upload_to='blogs_images/')
base = models.BooleanField(default=False)
thumbnail = models.BooleanField(default=False)
small = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

def __str__(self):
return f"{self.image} ({self.blog.title})"
Loading