From b198b1853860401c684054fff626fc2a2d76071e Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Sun, 15 Jun 2025 22:45:09 +0545 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20chore(apps):=20update=20app=20na?= =?UTF-8?q?mes=20and=20structures=20in=20various=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/__init__.py | 3 + apps/accounts/admin.py | 159 ++++++++++++++++++++++- apps/accounts/apps.py | 2 +- apps/accounts/backends.py | 17 +++ apps/accounts/migrations/0001_initial.py | 100 ++++++++++++++ apps/accounts/models.py | 149 ++++++++++++++++++++- apps/accounts/serializers.py | 45 +++++++ apps/accounts/urls.py | 10 ++ apps/accounts/views.py | 45 ++++++- apps/achievements/apps.py | 2 +- apps/dashboard/apps.py | 2 +- apps/events/apps.py | 2 +- apps/forum/apps.py | 2 +- apps/groups/__init__.py | 0 apps/groups/admin.py | 3 + apps/groups/apps.py | 6 + apps/groups/migrations/__init__.py | 0 apps/groups/models.py | 3 + apps/groups/tests.py | 3 + apps/groups/views.py | 3 + apps/jobs/apps.py | 2 +- apps/mentorship/apps.py | 2 +- apps/messaging/apps.py | 2 +- apps/news/apps.py | 2 +- apps/notifications/apps.py | 2 +- config/settings/base.py | 23 ++-- config/urls.py | 44 ++++--- 27 files changed, 588 insertions(+), 45 deletions(-) create mode 100644 apps/__init__.py create mode 100644 apps/accounts/backends.py create mode 100644 apps/accounts/migrations/0001_initial.py create mode 100644 apps/accounts/serializers.py create mode 100644 apps/accounts/urls.py create mode 100644 apps/groups/__init__.py create mode 100644 apps/groups/admin.py create mode 100644 apps/groups/apps.py create mode 100644 apps/groups/migrations/__init__.py create mode 100644 apps/groups/models.py create mode 100644 apps/groups/tests.py create mode 100644 apps/groups/views.py diff --git a/apps/__init__.py b/apps/__init__.py new file mode 100644 index 0000000..00cb628 --- /dev/null +++ b/apps/__init__.py @@ -0,0 +1,3 @@ +""" +This file makes the apps directory a Python package. +""" diff --git a/apps/accounts/admin.py b/apps/accounts/admin.py index 8c38f3f..1446d84 100644 --- a/apps/accounts/admin.py +++ b/apps/accounts/admin.py @@ -1,3 +1,160 @@ from django.contrib import admin +from django.contrib.auth.admin import UserAdmin +from django.utils.translation import gettext_lazy as _ +from .models import User, UserProfile -# Register your models here. + +class UserProfileInline(admin.StackedInline): + model = UserProfile + can_delete = False + verbose_name_plural = "Profile" + fk_name = "user" + fieldsets = ( + (_("Personal Info"), {"fields": ("avatar", "bio", "location")}), + (_("Education"), {"fields": ("graduation_year", "degree", "major")}), + ( + _("Professional Info"), + { + "fields": ( + "current_company", + "current_position", + "linkedin_url", + "website_url", + ) + }, + ), + (_("Preferences"), {"fields": ("privacy_level", "notification_preferences")}), + ( + _("Mentorship"), + {"fields": ("is_mentor_available", "mentor_skills", "is_job_seeking")}, + ), + ) + + +@admin.register(User) +class CustomUserAdmin(UserAdmin): + list_display = ( + "email", + "phone", + "first_name", + "last_name", + "is_active", + "is_alumni", + "is_staff_member", + "is_admin", + "last_active", + ) + list_filter = ( + "is_active", + "is_alumni", + "is_staff_member", + "is_admin", + "created_at", + "updated_at", + ) + search_fields = ("email", "phone", "first_name", "last_name") + ordering = ("-created_at",) + filter_horizontal = ( + "groups", + "user_permissions", + ) + + fieldsets = ( + (None, {"fields": ("email", "phone", "password")}), + (_("Personal Info"), {"fields": ("first_name", "last_name")}), + ( + _("Status"), + {"fields": ("is_active", "is_alumni", "is_staff_member", "is_admin")}, + ), + ( + _("Permissions"), + { + "fields": ("is_staff", "is_superuser", "groups", "user_permissions"), + }, + ), + ( + _("Important Dates"), + {"fields": ("last_login", "created_at", "updated_at", "last_active")}, + ), + ) + + readonly_fields = ("created_at", "updated_at", "last_active") + + add_fieldsets = ( + ( + None, + { + "classes": ("wide",), + "fields": ( + "email", + "phone", + "password1", + "password2", + "first_name", + "last_name", + ), + }, + ), + ) + + inlines = [UserProfileInline] + + def get_inline_instances(self, request, obj=None): + if not obj: + return [] + return super().get_inline_instances(request, obj) + + +@admin.register(UserProfile) +class UserProfileAdmin(admin.ModelAdmin): + list_display = ( + "user", + "graduation_year", + "current_company", + "is_mentor_available", + "is_job_seeking", + "privacy_level", + ) + list_filter = ( + "graduation_year", + "is_mentor_available", + "is_job_seeking", + "privacy_level", + "created_at", + ) + search_fields = ( + "user__email", + "user__first_name", + "user__last_name", + "current_company", + "degree", + "major", + ) + raw_id_fields = ("user",) + + fieldsets = ( + (_("User"), {"fields": ("user",)}), + (_("Personal Info"), {"fields": ("avatar", "bio", "location")}), + (_("Education"), {"fields": ("graduation_year", "degree", "major")}), + ( + _("Professional Info"), + { + "fields": ( + "current_company", + "current_position", + "linkedin_url", + "website_url", + ) + }, + ), + (_("Preferences"), {"fields": ("privacy_level", "notification_preferences")}), + ( + _("Mentorship"), + {"fields": ("is_mentor_available", "mentor_skills", "is_job_seeking")}, + ), + (_("Timestamps"), {"fields": ("created_at", "updated_at")}), + ) + readonly_fields = ("created_at", "updated_at") + + def get_queryset(self, request): + return super().get_queryset(request).select_related("user") diff --git a/apps/accounts/apps.py b/apps/accounts/apps.py index 3e3c765..bd9d60a 100644 --- a/apps/accounts/apps.py +++ b/apps/accounts/apps.py @@ -3,4 +3,4 @@ from django.apps import AppConfig class AccountsConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'accounts' + name = 'apps.accounts' diff --git a/apps/accounts/backends.py b/apps/accounts/backends.py new file mode 100644 index 0000000..c7652d8 --- /dev/null +++ b/apps/accounts/backends.py @@ -0,0 +1,17 @@ +from django.contrib.auth import get_user_model +from django.contrib.auth.backends import ModelBackend +from django.db.models import Q + +UserModel = get_user_model() + + +class EmailOrPhoneBackend(ModelBackend): + def authenticate(self, request, username=None, password=None, **kwargs): + try: + user = UserModel.objects.get(Q(email=username) | Q(phone=username)) + except UserModel.DoesNotExist: + return None + + if user.check_password(password) and self.user_can_authenticate(user): + return user + return None diff --git a/apps/accounts/migrations/0001_initial.py b/apps/accounts/migrations/0001_initial.py new file mode 100644 index 0000000..1aa33ac --- /dev/null +++ b/apps/accounts/migrations/0001_initial.py @@ -0,0 +1,100 @@ +# Generated by Django 5.2.3 on 2025-06-15 16:51 + +import django.core.validators +import django.db.models.deletion +import django.utils.timezone +import uuid +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=[ + ('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')), + ('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')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('email', models.EmailField(max_length=254, unique=True)), + ('phone', models.CharField(blank=True, max_length=10, null=True, unique=True, validators=[django.core.validators.RegexValidator(message='Phone number must be 10 digits.', regex='^\\d{10}$')])), + ('is_active', models.BooleanField(default=False)), + ('is_alumni', models.BooleanField(default=True)), + ('is_staff_member', models.BooleanField(default=False)), + ('is_admin', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('last_active', models.DateTimeField(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')), + ], + ), + migrations.CreateModel( + name='UserProfile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('avatar', models.ImageField(blank=True, null=True, upload_to='avatars/')), + ('bio', models.TextField(blank=True, max_length=500)), + ('location', models.CharField(blank=True, max_length=100)), + ('graduation_year', models.PositiveIntegerField(blank=True, null=True)), + ('degree', models.CharField(blank=True, max_length=100)), + ('major', models.CharField(blank=True, max_length=100)), + ('current_company', models.CharField(blank=True, max_length=100)), + ('current_position', models.CharField(blank=True, max_length=100)), + ('linkedin_url', models.URLField(blank=True)), + ('website_url', models.URLField(blank=True)), + ('privacy_level', models.CharField(choices=[('public', 'Public'), ('alumni_only', 'Alumni Only'), ('private', 'Private')], default='alumni_only', max_length=20)), + ('notification_preferences', models.JSONField(default=dict)), + ('is_mentor_available', models.BooleanField(default=False)), + ('mentor_skills', models.JSONField(default=list)), + ('is_job_seeking', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'User Profile', + 'verbose_name_plural': 'User Profiles', + }, + ), + migrations.AddIndex( + model_name='user', + index=models.Index(fields=['email'], name='user_email_idx'), + ), + migrations.AddIndex( + model_name='user', + index=models.Index(fields=['phone'], name='user_phone_idx'), + ), + migrations.AddIndex( + model_name='user', + index=models.Index(fields=['first_name', 'last_name'], name='user_name_idx'), + ), + migrations.AddIndex( + model_name='userprofile', + index=models.Index(fields=['graduation_year'], name='profile_grad_year_idx'), + ), + migrations.AddIndex( + model_name='userprofile', + index=models.Index(fields=['current_company'], name='profile_company_idx'), + ), + migrations.AddIndex( + model_name='userprofile', + index=models.Index(fields=['is_mentor_available'], name='profile_mentor_idx'), + ), + migrations.AddIndex( + model_name='userprofile', + index=models.Index(fields=['is_job_seeking'], name='profile_jobseek_idx'), + ), + ] diff --git a/apps/accounts/models.py b/apps/accounts/models.py index 71a8362..26bad19 100644 --- a/apps/accounts/models.py +++ b/apps/accounts/models.py @@ -1,3 +1,148 @@ -from django.db import models +import uuid +from django.contrib.auth.models import AbstractUser, BaseUserManager +from django.core.validators import RegexValidator +from django.db import models, IntegrityError +from django.conf import settings +from django.db.models.signals import post_save +from django.dispatch import receiver -# Create your models here. + +class CustomUserManager(BaseUserManager): + def create_user(self, email=None, phone=None, password=None, **extra_fields): + if not email and not phone: + raise ValueError("User must have an email or a phone number.") + + if email: + email = self.normalize_email(email) + + try: + user = self.model(email=email, phone=phone, **extra_fields) + + if not password: + raise ValueError("Password must be provided.") + user.set_password(password) + user.save(using=self._db) + return user + + except IntegrityError as e: + # Handle unique constraints more clearly + if "unique" in str(e).lower(): + if email and self.model.objects.filter(email=email).exists(): + raise ValueError("A user with this email already exists.") + if phone and self.model.objects.filter(phone=phone).exists(): + raise ValueError("A user with this phone number already exists.") + raise + + def create_superuser(self, email=None, phone=None, password=None, **extra_fields): + extra_fields.setdefault("is_staff", True) + extra_fields.setdefault("is_superuser", True) + extra_fields.setdefault("is_admin", True) + extra_fields.setdefault("is_active", True) + + if not password: + raise ValueError("Superuser must have a password.") + if not email: + raise ValueError("Superuser must have an email address.") + + return self.create_user( + email=email, phone=phone, password=password, **extra_fields + ) + + +class User(AbstractUser): + username = None + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + + email = models.EmailField(unique=True) + phone = models.CharField( + max_length=10, + unique=True, + null=True, + blank=True, + validators=[ + RegexValidator( + regex=r"^\d{10}$", + message="Phone number must be 10 digits.", + ) + ], + ) + + is_active = models.BooleanField(default=False) + is_alumni = models.BooleanField(default=True) + is_staff_member = models.BooleanField(default=False) + is_admin = models.BooleanField(default=False) + + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + last_active = models.DateTimeField(null=True, blank=True) + + USERNAME_FIELD = "email" + REQUIRED_FIELDS = ["first_name", "last_name", "phone"] + + objects = CustomUserManager() + + def __str__(self): + return f"{self.first_name} {self.last_name} ({self.email})" + + class Meta: + indexes = [ + models.Index(fields=["email"], name="user_email_idx"), + models.Index(fields=["phone"], name="user_phone_idx"), + models.Index(fields=["first_name", "last_name"], name="user_name_idx"), + ] + + +class UserProfile(models.Model): + user = models.OneToOneField( + settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="profile" + ) + avatar = models.ImageField(upload_to="avatars/", null=True, blank=True) + bio = models.TextField(max_length=500, blank=True) + location = models.CharField(max_length=100, blank=True) + + graduation_year = models.PositiveIntegerField(null=True, blank=True) + degree = models.CharField(max_length=100, blank=True) + major = models.CharField(max_length=100, blank=True) + + current_company = models.CharField(max_length=100, blank=True) + current_position = models.CharField(max_length=100, blank=True) + + linkedin_url = models.URLField(blank=True) + website_url = models.URLField(blank=True) + + privacy_level = models.CharField( + max_length=20, + choices=[ + ("public", "Public"), + ("alumni_only", "Alumni Only"), + ("private", "Private"), + ], + default="alumni_only", + ) + + notification_preferences = models.JSONField(default=dict) + is_mentor_available = models.BooleanField(default=False) + mentor_skills = models.JSONField(default=list) + is_job_seeking = 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.user.get_full_name()} - Profile" + + class Meta: + indexes = [ + models.Index(fields=["graduation_year"], name="profile_grad_year_idx"), + models.Index(fields=["current_company"], name="profile_company_idx"), + models.Index(fields=["is_mentor_available"], name="profile_mentor_idx"), + models.Index(fields=["is_job_seeking"], name="profile_jobseek_idx"), + ] + verbose_name = "User Profile" + verbose_name_plural = "User Profiles" + + +@receiver(post_save, sender=settings.AUTH_USER_MODEL) +def create_user_profile(sender, instance, created, **kwargs): + if created: + UserProfile.objects.create(user=instance) diff --git a/apps/accounts/serializers.py b/apps/accounts/serializers.py new file mode 100644 index 0000000..8fed198 --- /dev/null +++ b/apps/accounts/serializers.py @@ -0,0 +1,45 @@ +from rest_framework import serializers +from django.contrib.auth import get_user_model +from apps.accounts.models import UserProfile + +User = get_user_model() + + +class UserProfileSerializer(serializers.ModelSerializer): + class Meta: + model = UserProfile + exclude = ["id", "created_at", "updated_at"] # optional + read_only_fields = ["user"] + + +class UserSerializer(serializers.ModelSerializer): + profile = UserProfileSerializer(read_only=True) + password = serializers.CharField(write_only=True, min_length=8) + + class Meta: + model = User + fields = [ + "id", "first_name", "last_name", "email", "phone", + "is_active", "is_alumni", "is_staff_member", "is_admin", + "created_at", "updated_at", "last_active", "password", "profile" + ] + read_only_fields = [ + "id", "is_active", "is_alumni", "is_staff_member", "is_admin", + "created_at", "updated_at", "last_active", "profile" + ] + + def create(self, validated_data): + password = validated_data.pop("password") + user = User.objects.create_user(**validated_data) + user.set_password(password) + user.save() + return user + + def update(self, instance, validated_data): + password = validated_data.pop("password", None) + for attr, value in validated_data.items(): + setattr(instance, attr, value) + if password: + instance.set_password(password) + instance.save() + return instance diff --git a/apps/accounts/urls.py b/apps/accounts/urls.py new file mode 100644 index 0000000..ef080d5 --- /dev/null +++ b/apps/accounts/urls.py @@ -0,0 +1,10 @@ +from rest_framework.routers import DefaultRouter +from apps.accounts.views import UserViewSet, UserProfileViewSet + +router = DefaultRouter() +router.register(r"users", UserViewSet) +router.register(r"profiles", UserProfileViewSet) + +urlpatterns = [ + *router.urls, +] diff --git a/apps/accounts/views.py b/apps/accounts/views.py index 91ea44a..c4ddd0b 100644 --- a/apps/accounts/views.py +++ b/apps/accounts/views.py @@ -1,3 +1,44 @@ -from django.shortcuts import render +from rest_framework import viewsets, permissions +from django.contrib.auth import get_user_model +from apps.accounts.models import UserProfile +from apps.accounts.serializers import UserSerializer, UserProfileSerializer +from rest_framework.decorators import action +from rest_framework.response import Response -# Create your views here. +User = get_user_model() + + +class UserViewSet(viewsets.ModelViewSet): + queryset = User.objects.all() + serializer_class = UserSerializer + permission_classes = [permissions.IsAuthenticatedOrReadOnly] + + @action( + detail=False, + methods=["get"], + url_path="me", + permission_classes=[permissions.IsAuthenticated], + ) + def me(self, request): + serializer = self.get_serializer(request.user) + return Response(serializer.data) + + +class UserProfileViewSet(viewsets.ModelViewSet): + queryset = UserProfile.objects.select_related("user").all() + serializer_class = UserProfileSerializer + permission_classes = [permissions.IsAuthenticatedOrReadOnly] + + def perform_update(self, serializer): + serializer.save(user=self.request.user) + + @action( + detail=False, + methods=["get"], + url_path="me", + permission_classes=[permissions.IsAuthenticated], + ) + def my_profile(self, request): + profile = request.user.profile + serializer = self.get_serializer(profile) + return Response(serializer.data) diff --git a/apps/achievements/apps.py b/apps/achievements/apps.py index 2a635e2..21d27ba 100644 --- a/apps/achievements/apps.py +++ b/apps/achievements/apps.py @@ -3,4 +3,4 @@ from django.apps import AppConfig class AchievementsConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'achievements' + name = 'apps.achievements' diff --git a/apps/dashboard/apps.py b/apps/dashboard/apps.py index 7b1cc05..4a7ec75 100644 --- a/apps/dashboard/apps.py +++ b/apps/dashboard/apps.py @@ -3,4 +3,4 @@ from django.apps import AppConfig class DashboardConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'dashboard' + name = 'apps.dashboard' diff --git a/apps/events/apps.py b/apps/events/apps.py index 20f48f2..b30d560 100644 --- a/apps/events/apps.py +++ b/apps/events/apps.py @@ -3,4 +3,4 @@ from django.apps import AppConfig class EventsConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'events' + name = 'apps.events' diff --git a/apps/forum/apps.py b/apps/forum/apps.py index f0cb8de..b047d9f 100644 --- a/apps/forum/apps.py +++ b/apps/forum/apps.py @@ -3,4 +3,4 @@ from django.apps import AppConfig class ForumConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'forum' + name = 'apps.forum' diff --git a/apps/groups/__init__.py b/apps/groups/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/groups/admin.py b/apps/groups/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/apps/groups/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/groups/apps.py b/apps/groups/apps.py new file mode 100644 index 0000000..73fd2ae --- /dev/null +++ b/apps/groups/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class GroupsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.groups' diff --git a/apps/groups/migrations/__init__.py b/apps/groups/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/groups/models.py b/apps/groups/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/apps/groups/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/apps/groups/tests.py b/apps/groups/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/groups/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/groups/views.py b/apps/groups/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/apps/groups/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/apps/jobs/apps.py b/apps/jobs/apps.py index cae3796..7a3ef66 100644 --- a/apps/jobs/apps.py +++ b/apps/jobs/apps.py @@ -3,4 +3,4 @@ from django.apps import AppConfig class JobsConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'jobs' + name = 'apps.jobs' diff --git a/apps/mentorship/apps.py b/apps/mentorship/apps.py index 3370f52..30929b1 100644 --- a/apps/mentorship/apps.py +++ b/apps/mentorship/apps.py @@ -3,4 +3,4 @@ from django.apps import AppConfig class MentorshipConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'mentorship' + name = 'apps.mentorship' diff --git a/apps/messaging/apps.py b/apps/messaging/apps.py index a1b00c5..8e8af91 100644 --- a/apps/messaging/apps.py +++ b/apps/messaging/apps.py @@ -3,4 +3,4 @@ from django.apps import AppConfig class MessagingConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'messaging' + name = 'apps.messaging' diff --git a/apps/news/apps.py b/apps/news/apps.py index 44db8e5..fa78a81 100644 --- a/apps/news/apps.py +++ b/apps/news/apps.py @@ -3,4 +3,4 @@ from django.apps import AppConfig class NewsConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'news' + name = 'apps.news' diff --git a/apps/notifications/apps.py b/apps/notifications/apps.py index 001b4f9..03a81ff 100644 --- a/apps/notifications/apps.py +++ b/apps/notifications/apps.py @@ -3,4 +3,4 @@ from django.apps import AppConfig class NotificationsConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'notifications' + name = 'apps.notifications' diff --git a/config/settings/base.py b/config/settings/base.py index 2c490ca..121cae9 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -43,6 +43,11 @@ INSTALLED_APPS = [ AUTH_USER_MODEL = "accounts.User" +AUTHENTICATION_BACKENDS = [ + "apps.accounts.backends.EmailOrPhoneBackend", + "django.contrib.auth.backends.ModelBackend", +] + MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", @@ -85,35 +90,35 @@ DATABASES = { AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] # Internationalization -LANGUAGE_CODE = 'en-us' -TIME_ZONE = 'Asia/Kathmandu' +LANGUAGE_CODE = "en-us" +TIME_ZONE = "Asia/Kathmandu" USE_I18N = True USE_TZ = True # Static files (CSS, JavaScript, Images) STATIC_URL = "/static/" -STATICFILES_DIRS = [BASE_DIR / "static"] +# STATICFILES_DIRS = [BASE_DIR / "static"] MEDIA_URL = "/media/" MEDIA_ROOT = BASE_DIR / "media" # REST Framework configuration is imported from rest_framework.py # Default primary key field type -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" # CORS Configuration CORS_ALLOW_ALL_ORIGINS = True # Only for development @@ -152,7 +157,7 @@ if not DEBUG: CSRF_COOKIE_SECURE = True SECURE_BROWSER_XSS_FILTER = True SECURE_CONTENT_TYPE_NOSNIFF = True - X_FRAME_OPTIONS = 'DENY' + X_FRAME_OPTIONS = "DENY" SECURE_HSTS_SECONDS = 31536000 # 1 year SECURE_HSTS_INCLUDE_SUBDOMAINS = True SECURE_HSTS_PRELOAD = True diff --git a/config/urls.py b/config/urls.py index 91da3b4..40a54dc 100644 --- a/config/urls.py +++ b/config/urls.py @@ -14,6 +14,7 @@ Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ + from django.contrib import admin from django.urls import path, include from django.conf import settings @@ -30,30 +31,31 @@ from drf_spectacular.views import ( ) urlpatterns = [ - path('admin/', admin.site.urls), - + # path("admin/", admin.site.urls), # API Authentication - path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), - path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), - path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'), - + path("api/token/", TokenObtainPairView.as_view(), name="token_obtain_pair"), + path("api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"), + path("api/token/verify/", TokenVerifyView.as_view(), name="token_verify"), # API Documentation - path('api/schema/', SpectacularAPIView.as_view(), name='schema'), - path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), - path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'), - + path("api/schema/", SpectacularAPIView.as_view(), name="schema"), + path( + "api/schema/swagger-ui/", + SpectacularSwaggerView.as_view(url_name="schema"), + name="swagger-ui", + ), + path( + "api/schema/redoc/", + SpectacularRedocView.as_view(url_name="schema"), + name="redoc", + ), # API Apps - path('api/accounts/', include('apps.accounts.urls')), - path('api/events/', include('apps.events.urls')), - path('api/news/', include('apps.news.urls')), - path('api/jobs/', include('apps.jobs.urls')), - path('api/forum/', include('apps.forum.urls')), - path('api/mentorship/', include('apps.mentorship.urls')), - path('api/achievements/', include('apps.achievements.urls')), - path('api/groups/', include('apps.groups.urls')), - path('api/messaging/', include('apps.messaging.urls')), - path('api/notifications/', include('apps.notifications.urls')), - path('api/dashboard/', include('apps.dashboard.urls')), + path("api/accounts/", include("apps.accounts.urls")), +] + + +# Admin URLs - Keep at the end to prevent URL conflicts +urlpatterns += [ + path("", admin.site.urls), ] if settings.DEBUG: