From 3e4401313274a6e5e812deb269e36bcddf15814e Mon Sep 17 00:00:00 2001 From: mrtoine Date: Mon, 15 Dec 2025 16:02:34 +0100 Subject: [PATCH] =?UTF-8?q?Ajout=20des=20applications=20`blog`=20et=20`pro?= =?UTF-8?q?gression`=20avec=20mod=C3=A8les,=20vues,=20URLs=20et=20int?= =?UTF-8?q?=C3=A9gration=20dans=20le=20sitemap=20et=20les=20configurations?= =?UTF-8?q?=20du=20projet.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blog/__init__.py | 0 blog/admin.py | 9 +++++ blog/apps.py | 5 +++ blog/migrations/0001_initial.py | 30 ++++++++++++++++ blog/migrations/__init__.py | 0 blog/models.py | 16 +++++++++ blog/tests.py | 3 ++ blog/urls.py | 8 +++++ blog/views.py | 7 ++++ ...ter_course_options_alter_lesson_options.py | 21 ++++++++++++ courses/models.py | 8 +++++ devart/settings.py | 2 ++ devart/sitemap.py | 9 +++++ devart/urls.py | 2 ++ progression/__init__.py | 0 progression/admin.py | 15 ++++++++ progression/apps.py | 5 +++ progression/migrations/0001_initial.py | 34 +++++++++++++++++++ progression/migrations/__init__.py | 0 progression/models.py | 28 +++++++++++++++ progression/tests.py | 3 ++ progression/urls.py | 7 ++++ progression/views.py | 3 ++ 23 files changed, 215 insertions(+) create mode 100644 blog/__init__.py create mode 100644 blog/admin.py create mode 100644 blog/apps.py create mode 100644 blog/migrations/0001_initial.py create mode 100644 blog/migrations/__init__.py create mode 100644 blog/models.py create mode 100644 blog/tests.py create mode 100644 blog/urls.py create mode 100644 blog/views.py create mode 100644 courses/migrations/0004_alter_course_options_alter_lesson_options.py create mode 100644 progression/__init__.py create mode 100644 progression/admin.py create mode 100644 progression/apps.py create mode 100644 progression/migrations/0001_initial.py create mode 100644 progression/migrations/__init__.py create mode 100644 progression/models.py create mode 100644 progression/tests.py create mode 100644 progression/urls.py create mode 100644 progression/views.py diff --git a/blog/__init__.py b/blog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/blog/admin.py b/blog/admin.py new file mode 100644 index 0000000..879ff7e --- /dev/null +++ b/blog/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin +from .models import Post + +@admin.register(Post) +class PostAdmin(admin.ModelAdmin): + list_display = ('name', 'tags', 'slug', 'created_at') + list_filter = ('created_at',) + search_fields = ('name', 'tags') + prepopulated_fields = {"slug": ("name",)} \ No newline at end of file diff --git a/blog/apps.py b/blog/apps.py new file mode 100644 index 0000000..7930587 --- /dev/null +++ b/blog/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class BlogConfig(AppConfig): + name = 'blog' diff --git a/blog/migrations/0001_initial.py b/blog/migrations/0001_initial.py new file mode 100644 index 0000000..9c7c01b --- /dev/null +++ b/blog/migrations/0001_initial.py @@ -0,0 +1,30 @@ +# Generated by Django 6.0 on 2025-12-15 14:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Post', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200)), + ('tags', models.CharField(max_length=200)), + ('slug', models.SlugField()), + ('content', models.TextField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ], + options={ + 'verbose_name': 'Article', + 'verbose_name_plural': 'Articles', + }, + ), + ] diff --git a/blog/migrations/__init__.py b/blog/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/blog/models.py b/blog/models.py new file mode 100644 index 0000000..1ca60c0 --- /dev/null +++ b/blog/models.py @@ -0,0 +1,16 @@ +from django.db import models + +class Post(models.Model): + name = models.CharField(max_length=200) + tags = models.CharField(max_length=200) + slug = models.SlugField() + content = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + verbose_name = "Article" + verbose_name_plural = "Articles" + + def __str__(self): + return self.name \ No newline at end of file diff --git a/blog/tests.py b/blog/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/blog/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/blog/urls.py b/blog/urls.py new file mode 100644 index 0000000..902608b --- /dev/null +++ b/blog/urls.py @@ -0,0 +1,8 @@ +from django.urls import path +from . import views + +app_name = 'blog' +urlpatterns = [ + path('', views.blog_home, name='blog'), + path('/', views.blog_view_post, name='post_detail'), +] \ No newline at end of file diff --git a/blog/views.py b/blog/views.py new file mode 100644 index 0000000..fd8566d --- /dev/null +++ b/blog/views.py @@ -0,0 +1,7 @@ +from django.shortcuts import render + +def blog_home(request): + return "" + +def blog_view_post(request): + return "" \ No newline at end of file diff --git a/courses/migrations/0004_alter_course_options_alter_lesson_options.py b/courses/migrations/0004_alter_course_options_alter_lesson_options.py new file mode 100644 index 0000000..ff801fb --- /dev/null +++ b/courses/migrations/0004_alter_course_options_alter_lesson_options.py @@ -0,0 +1,21 @@ +# Generated by Django 6.0 on 2025-12-15 14:58 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('courses', '0003_comment_parent'), + ] + + operations = [ + migrations.AlterModelOptions( + name='course', + options={'verbose_name': 'Cours', 'verbose_name_plural': 'Cours'}, + ), + migrations.AlterModelOptions( + name='lesson', + options={'verbose_name': 'Leçon', 'verbose_name_plural': 'Leçons'}, + ), + ] diff --git a/courses/models.py b/courses/models.py index 3001eb5..3cc46d1 100644 --- a/courses/models.py +++ b/courses/models.py @@ -12,6 +12,10 @@ class Course(models.Model): updated_at = models.DateTimeField(auto_now=True) enable = models.BooleanField(default=True) + class Meta: + verbose_name = "Cours" + verbose_name_plural = "Cours" + def __str__(self): return self.name @@ -40,6 +44,10 @@ class Lesson(models.Model): is_premium = models.BooleanField(default=False) order = models.PositiveIntegerField() + class Meta: + verbose_name = "Leçon" + verbose_name_plural = "Leçons" + def clean(self): # Remplacer les chevrons par leurs équivalents HTML if self.content: diff --git a/devart/settings.py b/devart/settings.py index fa7d2d5..9bb9173 100644 --- a/devart/settings.py +++ b/devart/settings.py @@ -51,6 +51,8 @@ INSTALLED_APPS = [ 'core', 'courses', 'users', + 'progression', + 'blog', ] MIDDLEWARE = [ diff --git a/devart/sitemap.py b/devart/sitemap.py index 83ae446..a92ac9d 100644 --- a/devart/sitemap.py +++ b/devart/sitemap.py @@ -4,6 +4,7 @@ from django.urls import reverse # --- IMPORTS DEPUIS TES DIFFÉRENTES FEATURES --- from courses.models import Course from users.models import Profile +from blog.models import Post # --- SITEMAP : LES Cours --- class CourseSitemap(sitemaps.Sitemap): @@ -17,6 +18,14 @@ class CourseSitemap(sitemaps.Sitemap): # Assure-toi que ton modèle Course a bien une méthode get_absolute_url return item.get_absolute_url() +# --- SITEMAP : BLOG --- +class BlogSitemap(sitemaps.Sitemap): + changefreq = "weekly" + priority = 0.8 + + def location(self, item): + return item.get_absolute_url() + # --- SITEMAP : PAGES STATIQUES --- class StaticViewSitemap(sitemaps.Sitemap): priority = 0.5 diff --git a/devart/urls.py b/devart/urls.py index e198bae..b46feda 100644 --- a/devart/urls.py +++ b/devart/urls.py @@ -44,6 +44,8 @@ urlpatterns = [ path('courses/', include('courses.urls')), path('users/', include('users.urls')), + path('blog/', include('blog.urls')), + path('sitemap.xml', sitemap, {'sitemaps': sitemaps_dict}, name='django.contrib.sitemaps.views.sitemap'), path('robots.txt', robots_txt), ] diff --git a/progression/__init__.py b/progression/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/progression/admin.py b/progression/admin.py new file mode 100644 index 0000000..6dd7924 --- /dev/null +++ b/progression/admin.py @@ -0,0 +1,15 @@ +from django.contrib import admin +from .models import Progression +from courses.models import Course, Lesson + +@admin.register(Progression) +class ProgressionAdmin(admin.ModelAdmin): + list_display = ('user', 'course', 'get_percent', 'updated_at') + list_filter = ('course', 'updated_at') + search_fields = ('user__username', 'course__name') + + autocomplete_fields = ['course', 'completed_lessons'] + + def get_percent(self, obj): + return f"{obj.percent_completed}" + get_percent.short_description = 'Progression' \ No newline at end of file diff --git a/progression/apps.py b/progression/apps.py new file mode 100644 index 0000000..7bf7a21 --- /dev/null +++ b/progression/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ProgressionConfig(AppConfig): + name = 'progression' diff --git a/progression/migrations/0001_initial.py b/progression/migrations/0001_initial.py new file mode 100644 index 0000000..f2023ef --- /dev/null +++ b/progression/migrations/0001_initial.py @@ -0,0 +1,34 @@ +# Generated by Django 6.0 on 2025-12-15 14:25 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('courses', '0003_comment_parent'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Progression', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('completed_lessons', models.ManyToManyField(blank=True, related_name='completed_by', to='courses.lesson')), + ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_progress', to='courses.course')), + ('last_viewed_lesson', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='last_viewed_by', to='courses.lesson')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='progress', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Progression du cours', + 'unique_together': {('user', 'course')}, + }, + ), + ] diff --git a/progression/migrations/__init__.py b/progression/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/progression/models.py b/progression/models.py new file mode 100644 index 0000000..d8002a8 --- /dev/null +++ b/progression/models.py @@ -0,0 +1,28 @@ +from django.db import models +from django.conf import settings + +class Progression(models.Model): + user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='progress') + course = models.ForeignKey('courses.Course', on_delete=models.CASCADE, related_name='user_progress') + completed_lessons = models.ManyToManyField('courses.Lesson', blank=True, related_name='completed_by') + last_viewed_lesson = models.ForeignKey('courses.Lesson', on_delete=models.SET_NULL, null=True, blank=True, related_name='last_viewed_by') + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + unique_together = ('user', 'course') + verbose_name = "Progression du cours" + + def __str__(self): + return f"{self.user} - {self.course.name}" + + @property + def percent_completed(self): + from courses.models import Lesson + total_lessons = Lesson.objects.filter(module__course=self.course).count() + if total_lessons == 0: + return 0 + + completed_lessons = self.completed_lessons.count() + + return int((completed_lessons / total_lessons) * 100) \ No newline at end of file diff --git a/progression/tests.py b/progression/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/progression/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/progression/urls.py b/progression/urls.py new file mode 100644 index 0000000..a2fd910 --- /dev/null +++ b/progression/urls.py @@ -0,0 +1,7 @@ +from django.urls import path +from . import views + +app_name = 'progression' +urlpatterns = [ + +] \ No newline at end of file diff --git a/progression/views.py b/progression/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/progression/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here.