Add Premium feature with UI, model changes, and admin configuration
This commit is contained in:
parent
95111240bc
commit
abe4a1a965
7 changed files with 156 additions and 6 deletions
|
|
@ -4,4 +4,5 @@ from . import views
|
||||||
app_name = 'home'
|
app_name = 'home'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.home, name='home'),
|
path('', views.home, name='home'),
|
||||||
|
path('premium/<int:course_id>', views.premium, name='premium'),
|
||||||
]
|
]
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render, get_object_or_404
|
||||||
from courses.models import Course
|
from courses.models import Course
|
||||||
|
|
||||||
def home(request):
|
def home(request):
|
||||||
courses = Course.objects.order_by('-created_at')[:6]
|
courses = Course.objects.order_by('-created_at')[:6]
|
||||||
return render(request, 'home.html', {'courses': courses})
|
return render(request, 'home.html', {'courses': courses})
|
||||||
|
|
||||||
|
def premium(request, course_id):
|
||||||
|
"""Landing page présentant les avantages du Premium."""
|
||||||
|
course = get_object_or_404(Course, pk=course_id)
|
||||||
|
return render(request, 'premium.html', {'course': course})
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,41 @@
|
||||||
<ol class="tocLessons">
|
<ol class="tocLessons">
|
||||||
{% for item in group.list %}
|
{% for item in group.list %}
|
||||||
<li class="tocLesson{% if lesson and lesson.id == item.id %} current{% endif %}">
|
<li class="tocLesson{% if lesson and lesson.id == item.id %} current{% endif %}">
|
||||||
<a class="tocLink" href="{% url 'courses:lesson_detail' course.slug item.module.slug item.slug %}">
|
<a class="tocLink {% if item.is_premium and user.profile.is_premium == False %}premium{% endif %}" href="{% url 'courses:lesson_detail' course.slug item.module.slug item.slug %}">
|
||||||
{{ item.name }}
|
{{ item.name }} {% if item.is_premium %}<span class="premium-tag">PREMIUM</span>{% endif %}
|
||||||
{% if lesson and lesson.id == item.id %}<span class="tocCurrentTag">(cours actuel)</span>{% endif %}
|
{% if lesson and lesson.id == item.id %}<span class="tocCurrentTag">(cours actuel)</span>{% endif %}
|
||||||
</a>
|
</a>
|
||||||
{% if lesson and lesson.id == item.id %}
|
{% if lesson and lesson.id == item.id %}
|
||||||
<div class="lessonInline">
|
<div class="lessonInline">
|
||||||
<article class="lesson">
|
<div class="lesson">
|
||||||
{% if lesson.video_id %}
|
{% if lesson.video_id %}
|
||||||
VIDEO {{ lesson.video_id }}<br />
|
{% if not lesson.is_premium %}
|
||||||
|
VIDEO {{ lesson.video_id }}<br />
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
{% if not user.profile.is_premium %}
|
||||||
|
<div class="alert premium-lock" role="note" aria-live="polite">
|
||||||
|
<div style="display:flex; gap:16px; align-items:flex-start;">
|
||||||
|
<i class="fa-solid fa-lock" aria-hidden="true" style="font-size:20px; margin-top:2px; color: var(--muted);"></i>
|
||||||
|
<div>
|
||||||
|
<h4 style="margin:0 0 8px 0;">Contenu réservé aux membres Premium</h4>
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<p class="muted" style="margin:0 0 12px 0;">Devenez membre Premium pour accéder à la vidéo de ce cours.</p>
|
||||||
|
<div class="actions">
|
||||||
|
<a class="btn btn-primary" href="{% url 'home:premium' item.id %}">Découvrir Premium</a>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p class="muted" style="margin:0 0 12px 0;">Connectez-vous ou devenez membre Premium pour accéder à la vidéo.</p>
|
||||||
|
<div class="actions" style="display:flex; gap:8px; flex-wrap:wrap;">
|
||||||
|
<a class="btn btn-secondary" href="{% url 'login' %}?next={% url 'courses:lesson_detail' course.slug module.slug lesson.slug %}">Se connecter</a>
|
||||||
|
<a class="btn btn-primary" href="{% url 'courses:list' %}">Découvrir Premium</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ lesson.content|safe }}
|
{{ lesson.content|safe }}
|
||||||
</article>
|
</article>
|
||||||
|
|
|
||||||
78
templates/premium.html
Normal file
78
templates/premium.html
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
{% extends 'layout.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="hero hero-narrow">
|
||||||
|
<div class="hero-inner center">
|
||||||
|
<div class="lock-icon-container">
|
||||||
|
<div class="lock-ring"></div>
|
||||||
|
<i class="fa-solid fa-lock"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if course %}
|
||||||
|
<h1>Débloquez le cours "<span class="highlight">{{ course.name }}</span>"</h1>
|
||||||
|
<p class="hero-sub">Ce contenu est réservé aux membres Premium. Rejoignez-nous pour accéder immédiatement à ce projet et aux sources.</p>
|
||||||
|
{% else %}
|
||||||
|
<h1>Passez à la vitesse supérieure</h1>
|
||||||
|
<p class="hero-sub">Accédez aux cours complets, aux projets concrets et aux corrections détaillées pour progresser plus vite.</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="button-grp" style="justify-content:center; margin-top: 30px;">
|
||||||
|
{% if course %}
|
||||||
|
<a class="button cta-primary" href="">
|
||||||
|
<i class="fa-solid fa-unlock"></i> Débloquer ce cours maintenant
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a class="button cta-primary" href="{% url 'courses:list' %}">
|
||||||
|
<i class="fa-solid fa-book-open"></i> Voir le catalogue
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<a class="button cta-secondary" href="{% url 'home:home' %}">Retourner à l'accueil</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hero-trust" aria-hidden="true">
|
||||||
|
<span><i class="fa-solid fa-bolt"></i> Accès immédiat</span>
|
||||||
|
<span><i class="fa-solid fa-rotate"></i> Mises à jour incluses</span>
|
||||||
|
<span><i class="fa-solid fa-certificate"></i> Satisfait ou remboursé</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="hero-decor" aria-hidden="true"></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{% if course %}
|
||||||
|
<section class="course-teaser">
|
||||||
|
<div class="teaser-card">
|
||||||
|
<div class="teaser-visual">
|
||||||
|
<img src="{{ course.thumbnail.url }}" alt="{{ course.name }}">
|
||||||
|
</div>
|
||||||
|
<div class="teaser-content">
|
||||||
|
<h3>Ce que vous allez apprendre</h3>
|
||||||
|
<ul class="check-list">
|
||||||
|
<li><i class="fa-solid fa-check"></i> Créer une application complète de A à Z</li>
|
||||||
|
<li><i class="fa-solid fa-check"></i> Les bonnes pratiques professionnelles</li>
|
||||||
|
<li><i class="fa-solid fa-check"></i> Accès au code source complet</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<section class="features">
|
||||||
|
<div class="feature">
|
||||||
|
<div class="feat-ico"><i class="fa-solid fa-layer-group"></i></div>
|
||||||
|
<h3>Parcours complets</h3>
|
||||||
|
<p>Des modules structurés du niveau débutant à avancé.</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature">
|
||||||
|
<div class="feat-ico"><i class="fa-solid fa-screwdriver-wrench"></i></div>
|
||||||
|
<h3>Projets de A à Z</h3>
|
||||||
|
<p>Construisez des applications réelles et apprenez les bonnes pratiques.</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature">
|
||||||
|
<div class="feat-ico"><i class="fa-solid fa-video"></i></div>
|
||||||
|
<h3>Corrections détaillées</h3>
|
||||||
|
<p>Vidéos explicatives pas-à-pas et ressources téléchargeables.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -1,3 +1,24 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from .models import Profile
|
||||||
|
|
||||||
# Register your models here.
|
|
||||||
|
@admin.register(Profile)
|
||||||
|
class ProfileAdmin(admin.ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
'user',
|
||||||
|
'first_name',
|
||||||
|
'last_name',
|
||||||
|
'is_premium',
|
||||||
|
'birth_date',
|
||||||
|
)
|
||||||
|
search_fields = (
|
||||||
|
'user__username',
|
||||||
|
'first_name',
|
||||||
|
'last_name',
|
||||||
|
'biography',
|
||||||
|
)
|
||||||
|
list_filter = (
|
||||||
|
'is_premium',
|
||||||
|
'birth_date',
|
||||||
|
)
|
||||||
|
autocomplete_fields = ('user',)
|
||||||
|
|
|
||||||
18
users/migrations/0003_profile_is_premium.py
Normal file
18
users/migrations/0003_profile_is_premium.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 6.0 on 2025-12-10 21:27
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0002_alter_profile_avatar'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='profile',
|
||||||
|
name='is_premium',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -11,6 +11,7 @@ class Profile(models.Model):
|
||||||
last_name = models.CharField(max_length=64, blank=True)
|
last_name = models.CharField(max_length=64, blank=True)
|
||||||
birth_date = models.DateField(null=True, blank=True)
|
birth_date = models.DateField(null=True, blank=True)
|
||||||
biography = models.TextField(blank=True)
|
biography = models.TextField(blank=True)
|
||||||
|
is_premium = models.BooleanField(default=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.user.username
|
return self.user.username
|
||||||
Loading…
Add table
Add a link
Reference in a new issue