From fdc9bb8f23c873e9f3067e51a84b7248dcc2e4a0 Mon Sep 17 00:00:00 2001 From: AN <144544047+mindfreq@users.noreply.github.com> Date: Sat, 11 Jan 2025 12:11:18 +0000 Subject: [PATCH] u --- compose/local/django/start | 2 +- config/settings/base.py | 9 +-- config/settings/local.py | 31 +++++++- config/urls.py | 17 ++-- docs/users.rst | 2 +- lms/accounts/adapters.py | 6 +- lms/accounts/admin.py | 8 +- lms/accounts/migrations/0001_initial.py | 44 +++++++++++ .../migrations/0002_alter_customuser_role.py | 18 +++++ .../migrations/0003_alter_customuser_role.py | 18 +++++ .../migrations/0004_alter_customuser_role.py | 18 +++++ .../migrations/0005_alter_customuser_role.py | 18 +++++ ...stomuser_managers_alter_customuser_role.py | 23 ++++++ ...007_remove_customuser_username_and_more.py | 27 +++++++ lms/accounts/migrations/__init__.py | 0 lms/accounts/models.py | 49 +++++++++--- lms/accounts/serializers.py | 70 ++++++++-------- lms/accounts/urls.py | 14 +++- lms/accounts/views.py | 32 +++++--- lms/app/migrations/0001_initial.py | 79 +++++++++++++++++++ lms/app/migrations/__init__.py | 0 lms/conftest.py | 18 ++--- lms/templates/base.html | 4 +- 23 files changed, 407 insertions(+), 100 deletions(-) create mode 100644 lms/accounts/migrations/0001_initial.py create mode 100644 lms/accounts/migrations/0002_alter_customuser_role.py create mode 100644 lms/accounts/migrations/0003_alter_customuser_role.py create mode 100644 lms/accounts/migrations/0004_alter_customuser_role.py create mode 100644 lms/accounts/migrations/0005_alter_customuser_role.py create mode 100644 lms/accounts/migrations/0006_alter_customuser_managers_alter_customuser_role.py create mode 100644 lms/accounts/migrations/0007_remove_customuser_username_and_more.py create mode 100644 lms/accounts/migrations/__init__.py create mode 100644 lms/app/migrations/0001_initial.py create mode 100644 lms/app/migrations/__init__.py diff --git a/compose/local/django/start b/compose/local/django/start index 1549d13..550cd9a 100644 --- a/compose/local/django/start +++ b/compose/local/django/start @@ -1,6 +1,6 @@ #!/bin/bash -set -o errexit +# set -o errexit set -o pipefail set -o nounset diff --git a/config/settings/base.py b/config/settings/base.py index 643e88a..c411f66 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -102,7 +102,7 @@ MIGRATION_MODULES = {"sites": "lms.contrib.sites.migrations"} # ------------------------------------------------------------------------------ # https://docs.djangoproject.com/en/dev/ref/settings/#authentication-backends AUTHENTICATION_BACKENDS = [ - "django.contrib.auth.backends.ModelBackend", + # "django.contrib.auth.backends.ModelBackend", "allauth.account.auth_backends.AuthenticationBackend", ] # https://docs.djangoproject.com/en/dev/ref/settings/#auth-user-model @@ -136,8 +136,8 @@ AUTH_PASSWORD_VALIDATORS = [ # ------------------------------------------------------------------------------ # https://docs.djangoproject.com/en/dev/ref/settings/#middleware MIDDLEWARE = [ - "django.middleware.security.SecurityMiddleware", "corsheaders.middleware.CorsMiddleware", + "django.middleware.security.SecurityMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.locale.LocaleMiddleware", @@ -192,7 +192,6 @@ TEMPLATES = [ "django.template.context_processors.static", "django.template.context_processors.tz", "django.contrib.messages.context_processors.messages", - "lms.users.context_processors.allauth_settings", ], }, }, @@ -372,11 +371,11 @@ SIMPLE_JWT = { 'ROTATE_REFRESH_TOKENS': True, 'BLACKLIST_AFTER_ROTATION': True, 'ALGORITHM': 'HS256', - # 'SIGNING_KEY': SECRET_KEY, + 'SIGNING_KEY': 'SECRET_KEY', } # django-cors-headers - https://github.com/adamchainz/django-cors-headers#setup -CORS_URLS_REGEX = r"^/api/.*$" +# CORS_URLS_REGEX = r"^/api/.*$" # By Default swagger ui is available only to admin user(s). You can change permission classes to change that # See more configuration options at https://drf-spectacular.readthedocs.io/en/latest/settings.html#settings diff --git a/config/settings/local.py b/config/settings/local.py index 590ac3e..1444356 100644 --- a/config/settings/local.py +++ b/config/settings/local.py @@ -14,7 +14,35 @@ SECRET_KEY = env( default="DM837WrWz7KIfZM2eb4swzqGlIG0VhhAIFNXf9KgamMtT42DTkHIEXfpF4N9rh2Y", ) # https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts -ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1"] # noqa: S104 +ALLOWED_HOSTS = ["127.0.0.1", "localhost"] # حدد المضيفين المسموح بهم + +CSRF_TRUSTED_ORIGINS = [ + 'http://localhost:3000', + 'http://127.0.0.1:3000', +] +CORS_ORIGIN_ALLOW_ALL = True +CORS_ALLOW_CREDENTIALS = False + + +CORS_ALLOW_METHODS = [ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE", + "OPTIONS", +] + +CORS_ALLOW_HEADERS = [ + "content-type", + "authorization", + "accept", + "origin", + "user-agent", + "x-csrftoken", + "x-requested-with", +] + # CACHES # ------------------------------------------------------------------------------ @@ -83,4 +111,3 @@ CELERY_TASK_EAGER_PROPAGATES = True # ------------------------------------------------------------------------------ -SIMPLE_JWT["SIGNING_KEY"]=SECRET_KEY diff --git a/config/urls.py b/config/urls.py index 32857a7..7227ebd 100644 --- a/config/urls.py +++ b/config/urls.py @@ -4,13 +4,14 @@ from django.conf.urls.static import static from django.contrib import admin from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.urls import include -from django.urls import path +from django.urls import path, re_path from django.views import defaults as default_views from django.views.generic import TemplateView from drf_spectacular.views import SpectacularAPIView from drf_spectacular.views import SpectacularSwaggerView from rest_framework.authtoken.views import obtain_auth_token -from dj_rest_auth.views import PasswordResetConfirmView +from lms.accounts.views import * + urlpatterns = [ path("", TemplateView.as_view(template_name="pages/home.html"), name="home"), @@ -29,17 +30,11 @@ if settings.DEBUG: # API URLS urlpatterns += [ - path( - 'auth/password/reset/confirm///', - PasswordResetConfirmView.as_view(), - name='password_reset_confirm', - ), - path('auth/registration/', include('dj_rest_auth.registration.urls')), - path('auth/', include('dj_rest_auth.urls')), + + path('authw/', include('dj_rest_auth.urls')), path('api-auth/', include('rest_framework.urls', namespace='rest_framework')), - - path('auth/', include('lms.accounts.urls')), + path('app/', include('lms.app.urls')), diff --git a/docs/users.rst b/docs/users.rst index ebaad04..fd40414 100644 --- a/docs/users.rst +++ b/docs/users.rst @@ -9,7 +9,7 @@ even if the default User model is sufficient for you. This model behaves identically to the default user model, but you’ll be able to customize it in the future if the need arises. -.. automodule:: lms.users.models +.. automodule:: lms.accounts.models :members: :noindex: diff --git a/lms/accounts/adapters.py b/lms/accounts/adapters.py index 4f1aca1..8df3c14 100644 --- a/lms/accounts/adapters.py +++ b/lms/accounts/adapters.py @@ -1,8 +1,8 @@ from allauth.account.adapter import DefaultAccountAdapter -# from django.contrib.sites.models import Site +from django.contrib.sites.models import Site -# current_site = Site.objects.get_current() -site_domain = "current_site.domain" +current_site = Site.objects.get_current() +site_domain = current_site.domain class CustomAccountAdapter(DefaultAccountAdapter): diff --git a/lms/accounts/admin.py b/lms/accounts/admin.py index 23e1a41..9e60d79 100644 --- a/lms/accounts/admin.py +++ b/lms/accounts/admin.py @@ -7,7 +7,6 @@ class CustomUserAdmin(admin.ModelAdmin): # تخصيص الحقول fieldsets = ( - (None, {'fields': ('username', 'password')}), ('Personal Info', {'fields': ('email', 'full_name', 'role')}), ('Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions')}), ('Important Dates', {'fields': ('last_login', 'date_joined')}), @@ -16,12 +15,11 @@ class CustomUserAdmin(admin.ModelAdmin): add_fieldsets = ( (None, { 'classes': ('wide',), - 'fields': ('username', 'password1', 'password2', 'email', 'full_name', 'role'), + 'fields': ('password1', 'password2', 'email', 'full_name', 'role'), }), ) - list_display = ('username', 'email', 'full_name', 'role', 'is_staff', 'is_active') - search_fields = ('username', 'email', 'full_name') - ordering = ('username',) + list_display = ('email', 'full_name', 'role', 'is_staff', 'is_active') + search_fields = ('email', 'full_name') admin.site.register(CustomUser, CustomUserAdmin) diff --git a/lms/accounts/migrations/0001_initial.py b/lms/accounts/migrations/0001_initial.py new file mode 100644 index 0000000..c88ccbc --- /dev/null +++ b/lms/accounts/migrations/0001_initial.py @@ -0,0 +1,44 @@ +# Generated by Django 5.0.10 on 2025-01-11 07:41 + +import django.contrib.auth.models +import django.contrib.auth.validators +import django.utils.timezone +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='CustomUser', + 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')), + ('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')), + ('email', models.EmailField(max_length=254, unique=True)), + ('full_name', models.CharField(max_length=255, null=True)), + ('role', models.CharField(choices=[('admin', 'admin'), ('student', 'student'), ('instructor', 'instructor')], max_length=255, 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()), + ], + ), + ] diff --git a/lms/accounts/migrations/0002_alter_customuser_role.py b/lms/accounts/migrations/0002_alter_customuser_role.py new file mode 100644 index 0000000..3f6d46a --- /dev/null +++ b/lms/accounts/migrations/0002_alter_customuser_role.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.10 on 2025-01-11 07:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='customuser', + name='role', + field=models.CharField(choices=[('instructor', 'instructor'), ('admin', 'admin'), ('student', 'student')], max_length=255, null=True), + ), + ] diff --git a/lms/accounts/migrations/0003_alter_customuser_role.py b/lms/accounts/migrations/0003_alter_customuser_role.py new file mode 100644 index 0000000..27760af --- /dev/null +++ b/lms/accounts/migrations/0003_alter_customuser_role.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.10 on 2025-01-11 07:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0002_alter_customuser_role'), + ] + + operations = [ + migrations.AlterField( + model_name='customuser', + name='role', + field=models.CharField(choices=[('student', 'student'), ('admin', 'admin'), ('instructor', 'instructor')], max_length=255, null=True), + ), + ] diff --git a/lms/accounts/migrations/0004_alter_customuser_role.py b/lms/accounts/migrations/0004_alter_customuser_role.py new file mode 100644 index 0000000..9351733 --- /dev/null +++ b/lms/accounts/migrations/0004_alter_customuser_role.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.10 on 2025-01-11 07:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0003_alter_customuser_role'), + ] + + operations = [ + migrations.AlterField( + model_name='customuser', + name='role', + field=models.CharField(choices=[('student', 'student'), ('instructor', 'instructor'), ('admin', 'admin')], max_length=255, null=True), + ), + ] diff --git a/lms/accounts/migrations/0005_alter_customuser_role.py b/lms/accounts/migrations/0005_alter_customuser_role.py new file mode 100644 index 0000000..97b33a2 --- /dev/null +++ b/lms/accounts/migrations/0005_alter_customuser_role.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.10 on 2025-01-11 10:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0004_alter_customuser_role'), + ] + + operations = [ + migrations.AlterField( + model_name='customuser', + name='role', + field=models.CharField(choices=[('student', 'student'), ('admin', 'admin'), ('instructor', 'instructor')], max_length=255, null=True), + ), + ] diff --git a/lms/accounts/migrations/0006_alter_customuser_managers_alter_customuser_role.py b/lms/accounts/migrations/0006_alter_customuser_managers_alter_customuser_role.py new file mode 100644 index 0000000..0d19738 --- /dev/null +++ b/lms/accounts/migrations/0006_alter_customuser_managers_alter_customuser_role.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.10 on 2025-01-11 10:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0005_alter_customuser_role'), + ] + + operations = [ + migrations.AlterModelManagers( + name='customuser', + managers=[ + ], + ), + migrations.AlterField( + model_name='customuser', + name='role', + field=models.CharField(choices=[('instructor', 'instructor'), ('student', 'student'), ('admin', 'admin')], max_length=255, null=True), + ), + ] diff --git a/lms/accounts/migrations/0007_remove_customuser_username_and_more.py b/lms/accounts/migrations/0007_remove_customuser_username_and_more.py new file mode 100644 index 0000000..50d96d1 --- /dev/null +++ b/lms/accounts/migrations/0007_remove_customuser_username_and_more.py @@ -0,0 +1,27 @@ +# Generated by Django 5.0.10 on 2025-01-11 10:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0006_alter_customuser_managers_alter_customuser_role'), + ] + + operations = [ + migrations.RemoveField( + model_name='customuser', + name='username', + ), + migrations.AlterField( + model_name='customuser', + name='full_name', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AlterField( + model_name='customuser', + name='role', + field=models.CharField(blank=True, choices=[('student', 'Student'), ('instructor', 'Instructor'), ('admin', 'Admin')], max_length=20, null=True), + ), + ] diff --git a/lms/accounts/migrations/__init__.py b/lms/accounts/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lms/accounts/models.py b/lms/accounts/models.py index 1990e7d..7fd8a3d 100644 --- a/lms/accounts/models.py +++ b/lms/accounts/models.py @@ -1,18 +1,45 @@ from django.db import models -from django.contrib.auth.models import AbstractUser +from django.contrib.auth.models import AbstractUser, BaseUserManager + + +class CustomUserManager(BaseUserManager): + def create_user(self, email, password=None, **extra_fields): + if not email: + raise ValueError('The Email field must be set') + email = self.normalize_email(email) + user = self.model(email=email, **extra_fields) + user.set_password(password) + user.save(using=self._db) + return user + + def create_superuser(self, email, password=None, **extra_fields): + extra_fields.setdefault('is_staff', True) + extra_fields.setdefault('is_superuser', True) + return self.create_user(email, password, **extra_fields) + class CustomUser(AbstractUser): - role = { - ('student', 'student'), - ('instructor', 'instructor'), - ('admin', 'admin'), - } + # إزالة الحقل username من AbstractUser + username = None first_name = None - last_name = None - email = models.EmailField(unique=True) - full_name = models.CharField(max_length=255, null=True) - role = models.CharField(max_length=255, null=True, choices=role) + last_name = None + + # الحقول الخاصة بالمستخدم المخصص + email = models.EmailField(unique=True) + full_name = models.CharField(max_length=255, null=True, blank=True) + ROLE_CHOICES = [ + ('student', 'Student'), + ('instructor', 'Instructor'), + ('admin', 'Admin'), + ] + role = models.CharField(max_length=20, choices=ROLE_CHOICES, null=True, blank=True) + + # تخصيص مدير المستخدم + objects = CustomUserManager() + + # تحديد الحقول الأساسية + USERNAME_FIELD = 'email' + REQUIRED_FIELDS = [] # قائمة الحقول المطلوبة لإنشاء مستخدم عبر createsuperuser - def __str__(self): return self.email diff --git a/lms/accounts/serializers.py b/lms/accounts/serializers.py index ef668d9..92b0b54 100644 --- a/lms/accounts/serializers.py +++ b/lms/accounts/serializers.py @@ -5,84 +5,82 @@ from django.contrib.auth import authenticate from django.utils.translation import gettext_lazy as _ from allauth.account.models import EmailAddress from dj_rest_auth.registration.serializers import RegisterSerializer +from django.contrib.auth import get_user_model +from rest_framework.exceptions import ValidationError +from allauth.account.utils import send_email_confirmation +from rest_framework.response import Response +User = get_user_model() class CustomLoginSerializer(LoginSerializer): email = serializers.EmailField(required=True) - password = serializers.CharField(style={'input_type': 'password'}) + password = serializers.CharField(style={'input_type': 'password'}, write_only=True) def validate(self, attrs): email = attrs.get('email') password = attrs.get('password') if not email or not password: - raise serializers.ValidationError("Please enter both email and password.") + raise serializers.ValidationError(_("Please enter both email and password.")) - User = get_user_model() + # البحث عن المستخدم بالبريد الإلكتروني users = User.objects.filter(email=email) if not users.exists(): - raise serializers.ValidationError("Incorrect email.") + raise serializers.ValidationError(_("No account found with this email.")) if users.count() > 1: - raise serializers.ValidationError("Multiple users found with this email. Please contact support.") + raise serializers.ValidationError(_("Multiple accounts found with this email. Please contact support.")) user = users.first() if not user.check_password(password): - raise serializers.ValidationError("Incorrect password.") + raise serializers.ValidationError(_("Incorrect password.")) if not self.is_email_verified(user): - raise serializers.ValidationError("Email not verified. Please verify your email first.") + raise serializers.ValidationError(_("Email not verified. Please verify your email first.")) + # إضافة المستخدم إلى الـ attrs attrs['user'] = user return attrs - def is_email_verified(self, user): - if hasattr(user, 'email_verified'): - return user.email_verified - else: - try: - email_address = EmailAddress.objects.get(user=user, email=user.email) - return email_address.verified - except EmailAddress.DoesNotExist: - return False - - + """ + التحقق من حالة التحقق من البريد الإلكتروني. + """ + try: + # استخدام نموذج EmailAddress للتحقق + email_address = EmailAddress.objects.get(user=user, email=user.email) + return email_address.verified + except EmailAddress.DoesNotExist: + return False class CustomRegisterSerializer(RegisterSerializer): full_name = serializers.CharField(required=True) def save(self, request): + email = self.data.get('email') + + email_address = EmailAddress.objects.filter(email=email).first() + if email_address: + if email_address.verified: + raise ValidationError({'email': 'This email is already.'}) + else: + send_email_confirmation(request, email_address.user) + raise ValidationError({'email': 'A confirmation email has been sent. Please confirm your email.'}) + user = super().save(request) user.full_name = self.data.get('full_name', '') user.save() + + send_email_confirmation(request, user) return user - - - - - - - - - - - - - - - - - - diff --git a/lms/accounts/urls.py b/lms/accounts/urls.py index ad65a1d..7f641e6 100644 --- a/lms/accounts/urls.py +++ b/lms/accounts/urls.py @@ -1,5 +1,15 @@ -from django.urls import path +from django.urls import path, re_path, include +from dj_rest_auth.views import PasswordResetConfirmView from . import views + + urlpatterns = [ - path("change-email/", views.CustomConfirmEmailView.as_view(), name="change-email") + path('', include('dj_rest_auth.urls')), + path('registration/', include('dj_rest_auth.registration.urls')), + path("registration/account-confirm-email/", views.ConfirmEmailAPIView.as_view(),name="account_confirm_email",), + path( + 'password/reset/confirm///', + PasswordResetConfirmView.as_view(), + name='password_reset_confirm', + ), ] diff --git a/lms/accounts/views.py b/lms/accounts/views.py index 29bca1e..d0d9748 100644 --- a/lms/accounts/views.py +++ b/lms/accounts/views.py @@ -7,17 +7,27 @@ from rest_framework.permissions import AllowAny from .serializers import ChangeEmailSerializer -class CustomConfirmEmailView(APIView): +class ConfirmEmailAPIView(APIView): + permission_classes = [AllowAny] + def post(self, request, *args, **kwargs): - serializer = ChangeEmailSerializer(data=request.data) - if serializer.is_valid(): - user = request.user - new_email = serializer.validated_data['email'] + key = request.data.get("key") + if not key: + return Response({"detail": _("Key is required.")}, status=status.HTTP_400_BAD_REQUEST) - # تغيير البريد الإلكتروني - email_address, created = EmailAddress.objects.get_or_create(user=user, email=new_email) - if not email_address.verified: - send_email_confirmation(request, user, email=email_address.email) + try: + # Attempt to retrieve the email confirmation using HMAC key + email_confirmation = EmailConfirmationHMAC.from_key(key) + if email_confirmation is None: + # If HMAC fails, fallback to database key + email_confirmation = EmailConfirmation.objects.get(key=key) + except EmailConfirmation.DoesNotExist: + return Response({"detail": _("Invalid or expired key.")}, status=status.HTTP_400_BAD_REQUEST) - return Response({"message": "تم إرسال بريد تأكيد إلى البريد الإلكتروني الجديد."}, status=status.HTTP_200_OK) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file + if email_confirmation.email_address.verified: + return Response({"detail": _("Email is already verified.")}, status=status.HTTP_200_OK) + + # Verify the email + email_confirmation.confirm(request) + + return Response({"detail": _("Email successfully verified.")}, status=status.HTTP_200_OK) diff --git a/lms/app/migrations/0001_initial.py b/lms/app/migrations/0001_initial.py new file mode 100644 index 0000000..0d3af14 --- /dev/null +++ b/lms/app/migrations/0001_initial.py @@ -0,0 +1,79 @@ +# Generated by Django 5.0.10 on 2025-01-11 07:54 + +import django.db.models.deletion +import uuid +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='Course', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('title', models.CharField(max_length=255, verbose_name='Course Title')), + ('description', models.TextField(verbose_name='Course Description')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('instructor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='courses_taught', to=settings.AUTH_USER_MODEL, verbose_name='Instructor')), + ], + ), + migrations.CreateModel( + name='Certificate', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('issued_at', models.DateTimeField(auto_now_add=True, verbose_name='Issued At')), + ('certificate_file', models.FileField(upload_to='certificates/', verbose_name='Certificate File')), + ('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='certificates', to=settings.AUTH_USER_MODEL, verbose_name='Student')), + ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='certificates', to='app.course', verbose_name='Course')), + ], + ), + migrations.CreateModel( + name='Enrollment', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('enrolled_at', models.DateTimeField(auto_now_add=True, verbose_name='Enrollment Date')), + ('completed', models.BooleanField(default=False, verbose_name='Completed')), + ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='enrollments', to='app.course', verbose_name='Course')), + ('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='enrollments', to=settings.AUTH_USER_MODEL, verbose_name='Student')), + ], + ), + migrations.CreateModel( + name='Module', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('title', models.CharField(max_length=255, verbose_name='Module Title')), + ('description', models.TextField(verbose_name='Module Description')), + ('order', models.PositiveIntegerField(default=0, unique=True, verbose_name='Order')), + ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='modules', to='app.course', verbose_name='Course')), + ], + ), + migrations.CreateModel( + name='Lesson', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('title', models.CharField(max_length=255, verbose_name='Lesson Title')), + ('content', models.TextField(verbose_name='Lesson Content')), + ('order', models.PositiveIntegerField(default=0, verbose_name='Order')), + ('file', models.FileField(blank=True, null=True, upload_to='lesson_files/', verbose_name='Attached File')), + ('module', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lessons', to='app.module', verbose_name='Module')), + ], + ), + migrations.CreateModel( + name='Quiz', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('title', models.CharField(max_length=255, verbose_name='Quiz Title')), + ('questions', models.JSONField(null=True, verbose_name='Questions')), + ('module', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='quiz', to='app.module', verbose_name='Module')), + ], + ), + ] diff --git a/lms/app/migrations/__init__.py b/lms/app/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lms/conftest.py b/lms/conftest.py index 607ea94..a354717 100644 --- a/lms/conftest.py +++ b/lms/conftest.py @@ -1,14 +1,14 @@ -import pytest +# import pytest -from lms.users.models import User -from lms.users.tests.factories import UserFactory +# from lms.users.models import User +# from lms.users.tests.factories import UserFactory -@pytest.fixture(autouse=True) -def _media_storage(settings, tmpdir) -> None: - settings.MEDIA_ROOT = tmpdir.strpath +# @pytest.fixture(autouse=True) +# def _media_storage(settings, tmpdir) -> None: +# settings.MEDIA_ROOT = tmpdir.strpath -@pytest.fixture -def user(db) -> User: - return UserFactory() +# @pytest.fixture +# def user(db) -> User: +# return UserFactory() diff --git a/lms/templates/base.html b/lms/templates/base.html index d86942b..42eca22 100644 --- a/lms/templates/base.html +++ b/lms/templates/base.html @@ -78,9 +78,7 @@ - + {% if request.user.is_authenticated %}