diff --git a/assets/pasted-20260207-201453-28f355e3.jpg b/assets/pasted-20260207-201453-28f355e3.jpg new file mode 100644 index 0000000..97c946b Binary files /dev/null and b/assets/pasted-20260207-201453-28f355e3.jpg differ diff --git a/assets/vm-shot-2026-02-07T20-14-48-715Z.jpg b/assets/vm-shot-2026-02-07T20-14-48-715Z.jpg new file mode 100644 index 0000000..97c946b Binary files /dev/null and b/assets/vm-shot-2026-02-07T20-14-48-715Z.jpg differ diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 96bce55..dc4c78d 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc index 0b85e94..0de8fbb 100644 Binary files a/config/__pycache__/urls.cpython-311.pyc and b/config/__pycache__/urls.cpython-311.pyc differ diff --git a/config/settings.py b/config/settings.py index 291d043..24a7558 100644 --- a/config/settings.py +++ b/config/settings.py @@ -180,3 +180,6 @@ if EMAIL_USE_SSL: # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +LOGIN_REDIRECT_URL = '/' +LOGOUT_REDIRECT_URL = '/' diff --git a/config/urls.py b/config/urls.py index bcfc074..2001e5f 100644 --- a/config/urls.py +++ b/config/urls.py @@ -1,19 +1,3 @@ -""" -URL configuration for config project. - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/5.2/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: path('', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') -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 include, path from django.conf import settings @@ -21,9 +5,10 @@ from django.conf.urls.static import static urlpatterns = [ path("admin/", admin.site.urls), + path("accounts/", include("django.contrib.auth.urls")), path("", include("core.urls")), ] if settings.DEBUG: urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets") - urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) \ No newline at end of file diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index a5ed392..4a272e8 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000..7d41c72 Binary files /dev/null and b/core/__pycache__/forms.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index e061640..76f0a4f 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 5a69659..b4b4993 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 2a36fd6..1bf81ae 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/admin.py b/core/admin.py index 8c38f3f..df68cb9 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,3 +1,24 @@ from django.contrib import admin +from .models import Course, Module, Lesson, UserProgress -# Register your models here. +@admin.register(Course) +class CourseAdmin(admin.ModelAdmin): + list_display = ('title', 'created_at') + search_fields = ('title',) + +@admin.register(Module) +class ModuleAdmin(admin.ModelAdmin): + list_display = ('title', 'course', 'order') + list_filter = ('course',) + ordering = ('course', 'order') + +@admin.register(Lesson) +class LessonAdmin(admin.ModelAdmin): + list_display = ('title', 'module', 'order') + list_filter = ('module__course', 'module') + ordering = ('module', 'order') + +@admin.register(UserProgress) +class UserProgressAdmin(admin.ModelAdmin): + list_display = ('user', 'lesson', 'completed_at') + list_filter = ('user',) \ No newline at end of file diff --git a/core/forms.py b/core/forms.py new file mode 100644 index 0000000..7ffba8e --- /dev/null +++ b/core/forms.py @@ -0,0 +1,33 @@ +from django import forms +from django.contrib.auth.models import User +from .models import Profile, Course, Module, Lesson + +class UserUpdateForm(forms.ModelForm): + email = forms.EmailField() + + class Meta: + model = User + fields = ['username', 'email', 'first_name', 'last_name'] + +class ProfileUpdateForm(forms.ModelForm): + class Meta: + model = Profile + fields = ['bio', 'location', 'birth_date', 'avatar'] + widgets = { + 'birth_date': forms.DateInput(attrs={'type': 'date'}), + } + +class CourseForm(forms.ModelForm): + class Meta: + model = Course + fields = ['title', 'description', 'image'] + +class ModuleForm(forms.ModelForm): + class Meta: + model = Module + fields = ['title', 'order'] + +class LessonForm(forms.ModelForm): + class Meta: + model = Lesson + fields = ['title', 'content', 'order'] \ No newline at end of file diff --git a/core/management/__init__.py b/core/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/management/__pycache__/__init__.cpython-311.pyc b/core/management/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..309c8b7 Binary files /dev/null and b/core/management/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/management/commands/__init__.py b/core/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/management/commands/__pycache__/__init__.cpython-311.pyc b/core/management/commands/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..349369e Binary files /dev/null and b/core/management/commands/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/management/commands/__pycache__/seed_data.cpython-311.pyc b/core/management/commands/__pycache__/seed_data.cpython-311.pyc new file mode 100644 index 0000000..878f849 Binary files /dev/null and b/core/management/commands/__pycache__/seed_data.cpython-311.pyc differ diff --git a/core/management/commands/seed_data.py b/core/management/commands/seed_data.py new file mode 100644 index 0000000..fccb727 --- /dev/null +++ b/core/management/commands/seed_data.py @@ -0,0 +1,61 @@ +from django.core.management.base import BaseCommand +from core.models import Course, Module, Lesson + +class Command(BaseCommand): + help = 'Seeds initial Python course data' + + def handle(self, *args, **kwargs): + # Python Fundamentals + course1, created = Course.objects.get_or_create( + title='Python Fundamentals', + defaults={'description': 'A comprehensive guide to Python for absolute beginners. High-contrast learning.'} + ) + + m1, _ = Module.objects.get_or_create(course=course1, title='Getting Started', order=1) + m2, _ = Module.objects.get_or_create(course=course1, title='Control Flow', order=2) + m3, _ = Module.objects.get_or_create(course=course1, title='Data Structures', order=3) + + Lesson.objects.get_or_create( + module=m1, title='Installation & Setup', + defaults={'content': 'In this lesson, we will install Python 3.11 and set up a minimalist IDE like VS Code or just use a terminal.', 'order': 1} + ) + Lesson.objects.get_or_create( + module=m1, title='Variables & Data Types', + defaults={'content': 'Python is dynamically typed. Learn about integers, strings, and booleans.', 'order': 2} + ) + Lesson.objects.get_or_create( + module=m2, title='If-Else Statements', + defaults={'content': 'Logic flows. If this then that. Else something else.', 'order': 1} + ) + Lesson.objects.get_or_create( + module=m3, title='Lists & Tuples', + defaults={'content': 'Learn how to store multiple items in a single variable using lists and immutable tuples.', 'order': 1} + ) + Lesson.objects.get_or_create( + module=m3, title='Dictionaries & Sets', + defaults={'content': 'Master key-value pairs and unique collections of items.', 'order': 2} + ) + + # Advanced Python + course2, created = Course.objects.get_or_create( + title='Advanced Python', + defaults={'description': 'Take your Python skills to the next level with complex architectures and patterns.'} + ) + + am1, _ = Module.objects.get_or_create(course=course2, title='Object Oriented Programming', order=1) + am2, _ = Module.objects.get_or_create(course=course2, title='Decorators & Generators', order=2) + + Lesson.objects.get_or_create( + module=am1, title='Classes and Objects', + defaults={'content': 'Understand the core concepts of OOP: how to define classes and instantiate objects.', 'order': 1} + ) + Lesson.objects.get_or_create( + module=am1, title='Inheritance', + defaults={'content': 'Learn how to create subclasses that inherit attributes and methods from a parent class.', 'order': 2} + ) + Lesson.objects.get_or_create( + module=am2, title='Function Decorators', + defaults={'content': 'Enhance your functions without modifying their source code using decorators.', 'order': 1} + ) + + self.stdout.write(self.style.SUCCESS('Successfully seeded extended Python Lern data')) \ No newline at end of file diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..19f6299 --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,66 @@ +# Generated by Django 5.2.7 on 2026-02-05 15:28 + +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='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()), + ('image', models.ImageField(blank=True, null=True, upload_to='courses/')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ], + ), + migrations.CreateModel( + name='Module', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=255)), + ('order', models.PositiveIntegerField(default=0)), + ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='modules', to='core.course')), + ], + options={ + 'ordering': ['order'], + }, + ), + migrations.CreateModel( + name='Lesson', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=255)), + ('content', models.TextField()), + ('order', models.PositiveIntegerField(default=0)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('module', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lessons', to='core.module')), + ], + options={ + 'ordering': ['order'], + }, + ), + migrations.CreateModel( + name='UserProgress', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('completed_at', models.DateTimeField(auto_now_add=True)), + ('lesson', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.lesson')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'unique_together': {('user', 'lesson')}, + }, + ), + ] diff --git a/core/migrations/0002_profile.py b/core/migrations/0002_profile.py new file mode 100644 index 0000000..7815d45 --- /dev/null +++ b/core/migrations/0002_profile.py @@ -0,0 +1,27 @@ +# Generated by Django 5.2.7 on 2026-02-07 19:56 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Profile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('bio', models.TextField(blank=True, max_length=500)), + ('location', models.CharField(blank=True, max_length=100)), + ('birth_date', models.DateField(blank=True, null=True)), + ('avatar', models.ImageField(blank=True, null=True, upload_to='avatars/')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/core/migrations/0003_course_author.py b/core/migrations/0003_course_author.py new file mode 100644 index 0000000..f76afcd --- /dev/null +++ b/core/migrations/0003_course_author.py @@ -0,0 +1,21 @@ +# Generated by Django 5.2.7 on 2026-02-07 20:06 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0002_profile'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='course', + name='author', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='courses_created', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/core/migrations/__pycache__/0001_initial.cpython-311.pyc b/core/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..b437832 Binary files /dev/null and b/core/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0002_profile.cpython-311.pyc b/core/migrations/__pycache__/0002_profile.cpython-311.pyc new file mode 100644 index 0000000..5e6f4de Binary files /dev/null and b/core/migrations/__pycache__/0002_profile.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0003_course_author.cpython-311.pyc b/core/migrations/__pycache__/0003_course_author.cpython-311.pyc new file mode 100644 index 0000000..43ed5e6 Binary files /dev/null and b/core/migrations/__pycache__/0003_course_author.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 71a8362..e701445 100644 --- a/core/models.py +++ b/core/models.py @@ -1,3 +1,72 @@ from django.db import models +from django.contrib.auth.models import User +from django.db.models.signals import post_save +from django.dispatch import receiver -# Create your models here. +class Profile(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + bio = models.TextField(max_length=500, blank=True) + location = models.CharField(max_length=100, blank=True) + birth_date = models.DateField(null=True, blank=True) + avatar = models.ImageField(upload_to='avatars/', null=True, blank=True) + + def __str__(self): + return f"{self.user.username}'s Profile" + +@receiver(post_save, sender=User) +def create_user_profile(sender, instance, created, **kwargs): + if created: + Profile.objects.get_or_create(user=instance) + +@receiver(post_save, sender=User) +def save_user_profile(sender, instance, **kwargs): + if hasattr(instance, 'profile'): + instance.profile.save() + else: + Profile.objects.create(user=instance) + +class Course(models.Model): + author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='courses_created', null=True, blank=True) + title = models.CharField(max_length=255) + description = models.TextField() + image = models.ImageField(upload_to='courses/', null=True, blank=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + return self.title + +class Module(models.Model): + course = models.ForeignKey(Course, related_name='modules', on_delete=models.CASCADE) + title = models.CharField(max_length=255) + order = models.PositiveIntegerField(default=0) + + class Meta: + ordering = ['order'] + + def __str__(self): + return f"{self.course.title} - {self.title}" + +class Lesson(models.Model): + module = models.ForeignKey(Module, related_name='lessons', on_delete=models.CASCADE) + title = models.CharField(max_length=255) + content = models.TextField() + order = models.PositiveIntegerField(default=0) + created_at = models.DateTimeField(auto_now_add=True) + + class Meta: + ordering = ['order'] + + def __str__(self): + return self.title + +class UserProgress(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE) + lesson = models.ForeignKey(Lesson, on_delete=models.CASCADE) + completed_at = models.DateTimeField(auto_now_add=True) + + class Meta: + unique_together = ('user', 'lesson') + + def __str__(self): + return f"{self.user.username} completed {self.lesson.title}" \ No newline at end of file diff --git a/core/templates/base.html b/core/templates/base.html index 1e7e5fb..5a629d3 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -1,25 +1,109 @@ +{% load static %} -
- -