Add initial migrations, admin configurations, and base CSS for the project

This commit is contained in:
mrtoine 2025-12-10 20:18:51 +01:00
parent 16897b6010
commit 8fe6fe5390
19 changed files with 2101 additions and 68 deletions

0
core/__init__.py Normal file
View file

26
core/admin.py Normal file
View file

@ -0,0 +1,26 @@
from django.contrib import admin
from .models import SiteSettings
@admin.register(SiteSettings)
class SiteSettingsAdmin(admin.ModelAdmin):
# On empêche d'ajouter une nouvelle config s'il en existe déjà une
def has_add_permission(self, request):
return not SiteSettings.objects.exists()
# On empêche de supprimer la config (trop dangereux)
def has_delete_permission(self, request, obj=None):
return False
# Petite astuce visuelle pour l'admin
fieldsets = (
('Général', {
'fields': ('site_name', 'site_logo')
}),
('Réseaux Sociaux', {
'fields': ('facebook_url', 'twitter_url', 'youtube_url'),
'classes': ('collapse',) # Cache cette section par défaut pour alléger
}),
('Contact', {
'fields': ('contact_email',)
}),
)

5
core/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class CoreConfig(AppConfig):
name = 'core'

View file

@ -0,0 +1,33 @@
# Generated by Django 6.0 on 2025-12-10 18:32
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='SiteSettings',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('site_name', models.CharField(default='Mon Super Site', max_length=200)),
('site_logo', models.ImageField(blank=True, upload_to='settings/')),
('contact_email', models.EmailField(blank=True, max_length=254)),
('facebook_url', models.URLField(blank=True)),
('twitter_url', models.URLField(blank=True)),
('youtube_url', models.URLField(blank=True)),
('instagram_url', models.URLField(blank=True)),
('linkedin_url', models.URLField(blank=True)),
('github_url', models.URLField(blank=True)),
],
options={
'verbose_name': 'Réglages du site',
'verbose_name_plural': 'Réglages du site',
},
),
]

View file

29
core/models.py Normal file
View file

@ -0,0 +1,29 @@
from django.db import models
class SiteSettings(models.Model):
site_name = models.CharField(max_length=200, default="Mon Super Site")
site_logo = models.ImageField(upload_to='settings/', blank=True)
contact_email = models.EmailField(blank=True)
# Réseaux sociaux
facebook_url = models.URLField(blank=True)
twitter_url = models.URLField(blank=True)
youtube_url = models.URLField(blank=True)
instagram_url = models.URLField(blank=True)
linkedin_url = models.URLField(blank=True)
github_url = models.URLField(blank=True)
# L'astuce pour qu'il n'y ait qu'un seul réglage
def save(self, *args, **kwargs):
self.pk = 1 # On force l'ID à 1. Si tu sauvegardes, ça écrase l'existant.
super(SiteSettings, self).save(*args, **kwargs)
def delete(self, *args, **kwargs):
pass # On empêche la suppression. On ne peut pas supprimer les réglages.
def __str__(self):
return "Configuration Générale"
class Meta:
verbose_name = "Réglages du site"
verbose_name_plural = "Réglages du site"

3
core/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
core/views.py Normal file
View file

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View file

@ -0,0 +1,59 @@
# Generated by Django 6.0 on 2025-12-10 18:04
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')),
('name', models.CharField(max_length=200)),
('slug', models.SlugField(unique=True)),
('tags', models.CharField(max_length=200)),
('thumbnail', models.ImageField(default='default.jpg', upload_to='thumbnails/courses/')),
('description', models.TextField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('enable', models.BooleanField(default=True)),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Module',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200)),
('slug', models.SlugField()),
('description', models.TextField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('enable', models.BooleanField(default=True)),
('order', models.PositiveIntegerField()),
('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='modules', to='courses.course')),
],
),
migrations.CreateModel(
name='Lesson',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200)),
('slug', models.SlugField()),
('content', models.TextField()),
('video_id', models.CharField(blank=True, max_length=200)),
('is_premium', models.BooleanField(default=False)),
('order', models.PositiveIntegerField()),
('module', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lessons', to='courses.module')),
],
),
]

BIN
data78HyT4mloSq_Gt.sqlite3 Normal file

Binary file not shown.

1670
static/css/app.css Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,11 @@
/* Theme bridge (DARK) 2025 design system
Minimal overrides; colors and components come from app.css tokens. */
html {
background-color: var(--bg);
color: var(--text);
color-scheme: dark;
}
nav.site-nav {
background: var(--nav-bg);
}

View file

@ -0,0 +1,14 @@
/* Theme bridge (LIGHT) 2025 design system
This file intentionally contains minimal overrides. The full palette,
components and tokens live in css/app.css using CSS variables.
Keeping this file ensures JS can swap theme stylesheets without FOUC. */
/* Prefer light-friendly scrollbar & subtle touches without overriding tokens */
html[data-theme='light'] {
color-scheme: light;
}
/* Optional: ensure nav backdrop looks crisp on light */
nav.site-nav {
background: var(--nav-bg);
}

View file

@ -2,18 +2,54 @@
{% block content %}
<section class="hero">
<div class="hero-inner">
<h1>Apprenez à coder de A à Z</h1>
<p class="hero-sub">Des cours gratuits et payants, structurés et concrets, pour progresser rapidement en programmation.</p>
<div class="button-grp hero-cta">
<a class="button cta-primary" href="{% url 'register' %}"><i class="fa-solid fa-play"></i> Commencer gratuitement</a>
<a class="button cta-secondary" href="{% url 'courses:list' %}"><i class="fa-solid fa-book"></i> Voir les cours</a>
<div class="hero-decor" aria-hidden="true"></div>
<div class="hero-inner hero-split">
<div class="hero-text">
<h1>Apprenez à coder de A à Z</h1>
<p class="hero-sub">Des cours gratuits et payants, structurés et concrets, pour progresser rapidement en programmation.</p>
<div class="badge-row" aria-hidden="true">
<span class="badge"><i class="fa-solid fa-code"></i> Logiciel, Web, Jeux Vidéos</span>
<span class="badge"><i class="fa-solid fa-graduation-cap"></i> Pédagogie claire</span>
<span class="badge"><i class="fa-solid fa-laptop-code"></i> Projets concrets</span>
</div>
<div class="button-grp hero-cta">
<a class="button cta-primary" href="{% url 'register' %}"><i class="fa-solid fa-play"></i> Commencer gratuitement</a>
<a class="button cta-secondary" href="{% url 'courses:list' %}"><i class="fa-solid fa-book"></i> Voir les cours</a>
</div>
<div class="hero-trust">
<span><i class="fa-solid fa-check"></i> Pas de carte requise pour commencer</span>
<span><i class="fa-solid fa-clock"></i> À votre rythme</span>
<span><i class="fa-solid fa-certificate"></i> Accès premium en option</span>
</div>
</div>
<div class="hero-trust">
<span><i class="fa-solid fa-check"></i> Pas de carte requise pour commencer</span>
<span><i class="fa-solid fa-clock"></i> À votre rythme</span>
<span><i class="fa-solid fa-certificate"></i> Accès premium en option</span>
<div class="hero-visual">
<div class="code-window">
<div class="window-header">
<div class="dots">
<span class="dot red"></span>
<span class="dot yellow"></span>
<span class="dot green"></span>
</div>
<span class="filename">main.py — PartirDeZero</span>
</div>
<div class="window-content">
<pre class="code-block"><code><span class="line"><span class="qn">def</span> <span class="fn">devenir_dev_autonome</span>(etudiant):</span>
<span class="line"> <span class="cm"># Objectif: Maîtriser Python & Django</span></span>
<span class="line"> motivation = <span class="kw">True</span></span>
<span class="line"> projet_concret = <span class="st">"Mon Portfolio"</span></span>
<span class="line"></span>
<span class="line"> <span class="qn">if</span> motivation <span class="ow">and</span> etudiant.<span class="fn">suit_le_cours</span>():</span>
<span class="line"> etudiant.<span class="fn">skills</span>.<span class="fn">append</span>(<span class="st">"Backend"</span>)</span>
<span class="line"> <span class="qn">return</span> <span class="st">f"Succès : {projet_concret} déployé !"</span></span>
<span class="line"></span>
<span class="line"><span class="cm"># Lancez votre carrière aujourd'hui</span></span>
<span class="line"><span class="fn">print</span>(<span class="fn">devenir_dev_autonome</span>(<span class="st">"Toi"</span>))</span></code></pre>
</div>
</div>
</div>
</div>
</section>
@ -37,7 +73,7 @@
{% block course %}
{% include "courses/partials/list.html" %}
{% endblock %}
<section class="pricing-teaser">
<h2>Gratuit pour commencer, Premium pour aller plus loin</h2>
<ul class="ul-arrow">
@ -51,11 +87,11 @@
</div>
</section>
<section class="carousel">
<section class="carousel" aria-label="Cours en vedette">
<h2>Cours en vedette</h2>
<div class="carousel-track">
<div class="carousel-track" role="list">
{% for course in courses|slice:":6" %}
<a class="carousel-item card" href="{% url 'courses:show' course.id course.name|slugify %}">
<a class="carousel-item card" href="{% url 'courses:show' course.id course.name|slugify %}" role="listitem">
<div class="ratio-16x9">
<img src="{{ course.thumbnail.url }}" alt="{{ course.name }}" loading="lazy">
</div>
@ -74,15 +110,21 @@
<h2>Ils progressent avec Partir de zéro</h2>
<div class="testimonials-grid">
<div class="testimonial">
<p>“Des cours clairs et progressifs. Jai enfin compris Django et jai lancé mon premier projet.”</p>
<blockquote>
<p>Des cours clairs et progressifs. Jai enfin compris Django et jai lancé mon premier projet.</p>
</blockquote>
<div class="who">Alexandre — Débutant devenu autonome</div>
</div>
<div class="testimonial">
<p>“Lapproche projet et les explications pas-à-pas mont fait gagner des semaines.”</p>
<blockquote>
<p>Lapproche projet et les explications pas-à-pas mont fait gagner des semaines.</p>
</blockquote>
<div class="who">Sarah — Étudiante en informatique</div>
</div>
<div class="testimonial">
<p>“Parfait pour reprendre les bases et aller plus loin. Le mode sombre est top 👌.”</p>
<blockquote>
<p>Parfait pour reprendre les bases et aller plus loin. Le mode sombre est top 👌.</p>
</blockquote>
<div class="who">Yassine — Dev front-end</div>
</div>
</div>

View file

@ -1,16 +1,40 @@
{% block 'profile-nav' %}
<div class="profile-header">
<h2>Profil de {{ user.username }}</h2>
<img src="/{{ user.profile.avatar }}" alt="Profile Picture" class="profile-picture">
</div>
<!-- On affiche le menu que si l'id du profil est égale à celui de l'utilisateur -->
{% if user.id == request.user.id %}
<div class="profile-nav">
<ul>
<li><a href="{% url 'profile' %}">Profil</a></li>
<li><a href="{% url 'account_update' %}">Paramètre de compte</a></li>
<li><a href="{% url 'user_courses' %}">Mes cours</a></li>
<div class="profile-cover">
<div class="profile-cover-bg"></div>
<div class="profile-cover-content">
<img src="/{{ user.profile.avatar }}" alt="Avatar de {{ user.username }}" class="profile-avatar">
<div class="profile-identity">
<h1 class="profile-name">{{ user.profile.first_name|default:user.username }} {{ user.profile.last_name }}</h1>
<div class="profile-username">@{{ user.username }}</div>
{% if user.profile.biography %}
<p class="profile-bio">{{ user.profile.biography }}</p>
{% endif %}
<ul class="profile-meta">
<li><i class="fa-regular fa-calendar"></i> Inscrit depuis {{ user.date_joined|date:"F Y" }}</li>
<li>
<i class="fa-solid fa-book"></i>
{% with user.course_set.count as courses_count %}
{{ courses_count }} cours
{% endwith %}
</li>
</ul>
</div>
{% endif %}
{% endblock %}
<div class="profile-actions-rail">
{% if user.id == request.user.id %}
<a href="{% url 'profile_update' %}" class="btn btn-primary">Éditer le profil</a>
<a href="{% url 'account_update' %}" class="btn btn-secondary">Paramètres</a>
{% else %}
<button class="btn btn-primary" disabled>Suivre</button>
<button class="btn btn-secondary" disabled>Message</button>
{% endif %}
</div>
</div>
</div>
<nav class="profile-tabs">
<ul>
<li class="active"><a href="#about">À propos</a></li>
{% if user.id == request.user.id %}
<li><a href="{% url 'user_courses' %}">Mes cours</a></li>
{% endif %}
</ul>
</nav>

View file

@ -1,16 +1,31 @@
{% extends 'layout.html' %}
{% block content %}
<section class="profile-section">
{% block profile-nav %}
{% include "../partials/_profile_nav.html" %}
{% endblock %}
<div class="profile-details">
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ user_form.as_p }}
<button type="submit" class="btn btn-primary">Mettre à jour</button>
</form>
</div>
{% include "../partials/_profile_nav.html" %}
</section>
<div class="profile-content">
<div class="profile-grid">
<div class="profile-card">
<h3>Paramètres du compte</h3>
<form method="post">
{% csrf_token %}
{{ user_form.as_p }}
<div class="text-right" style="margin-top:12px; display:flex; gap:8px; justify-content:flex-end;">
<a href="{% url 'profile' %}" class="btn btn-secondary">Annuler</a>
<button type="submit" class="btn btn-primary">Enregistrer</button>
</div>
</form>
</div>
<div class="profile-card">
<h3>Conseils</h3>
<ul class="profile-about-list">
<li>Assurez-vous que votre adresse email est valide pour récupérer votre compte.</li>
<li>Utilisez un mot de passe unique et robuste (mélange de lettres, chiffres, symboles).</li>
<li>Votre nom dutilisateur est visible publiquement — choisissez-le avec soin.</li>
</ul>
</div>
</div>
</div>
</section>
{% endblock %}

View file

@ -1,13 +1,49 @@
{% extends 'layout.html' %}
{% block content %}
<section class="profile-section">
{% block profile-nav %}
{% include "../partials/_profile_nav.html" %}
{% endblock %}
<div class="profile-details">
<p><strong>Pseudo :</strong> {{ user.username }}</p>
<p><strong>Date d'nscription :</strong> {{ user.date_joined|date:"F j, Y" }}</p>
<p><strong>Bio de profil :</strong> {{ user.profile.biography }}</p>
{% include "../partials/_profile_nav.html" %}
<div class="profile-content">
<div class="profile-grid">
<div class="profile-card" id="about">
<h3>À propos</h3>
<ul class="profile-about-list">
<li><strong>Pseudo</strong> : {{ user.username }}</li>
<li><strong>Nom complet</strong> : {{ user.profile.first_name }} {{ user.profile.last_name }}</li>
<li><strong>Inscription</strong> : {{ user.date_joined|date:"j F Y" }}</li>
</ul>
{% if user.profile.biography %}
<p class="profile-bio-body">{{ user.profile.biography }}</p>
{% else %}
<p class="profile-bio-body muted">Aucune bio pour le moment.</p>
{% endif %}
</div>
<div class="profile-card">
<h3>Cours publiés</h3>
{% with courses=user.course_set.all %}
{% if courses %}
<ul class="profile-courses">
{% for course in courses|slice:":6" %}
<li>
<a href="{% url 'courses:show' course_id=course.id course_name=course.slug %}">
<img src="/{{ course.thumbnail }}" alt="{{ course.name }}" class="course-thumb-mini">
<span>{{ course.name }}</span>
</a>
</li>
{% endfor %}
</ul>
{% else %}
<p class="muted">Aucun cours publié.</p>
{% endif %}
{% endwith %}
</div>
<div class="profile-card">
<h3>Activité</h3>
<p class="muted">Le fil dactivité de cet utilisateur arrive bientôt.</p>
</div>
</div>
</div>
</section>
{% endblock %}

View file

@ -1,18 +1,53 @@
{% extends 'layout.html' %}
{% block content %}
<section class="profile-section">
{% block profile-nav %}
{% include "../partials/_profile_nav.html" %}
{% endblock %}
<div class="profile-details">
<p><strong>Username:</strong> {{ user.username }}</p>
<p><strong>Email:</strong> {{ user.email }}</p>
<p><strong>Nom complet:</strong> {{ user.profile.first_name }} {{ user.profile.last_name }}</p>
<p><strong>Inscription:</strong> {{ user.date_joined|date:"F j, Y" }}</p>
<p><strong>Bio:</strong> {{ user.profile.biography }}</p>
</div>
<div class="profile-actions">
<a href="{% url 'profile_update' %}" class="btn btn-primary">Modifier le profil publique</a><br><br>
{% include "../partials/_profile_nav.html" %}
<div class="profile-content">
<div class="profile-grid">
<div class="profile-card" id="about">
<h3>À propos</h3>
<ul class="profile-about-list">
<li><strong>Pseudo</strong> : {{ user.username }}</li>
<li><strong>Nom complet</strong> : {{ user.profile.first_name }} {{ user.profile.last_name }}</li>
<li><strong>Email</strong> : {{ user.email }}</li>
<li><strong>Inscription</strong> : {{ user.date_joined|date:"j F Y" }}</li>
</ul>
{% if user.profile.biography %}
<p class="profile-bio-body">{{ user.profile.biography }}</p>
{% else %}
<p class="profile-bio-body muted">Ajoutez une bio pour présenter votre parcours.</p>
{% endif %}
</div>
<div class="profile-card">
<h3>Mes cours</h3>
{% with courses=user.course_set.all %}
{% if courses %}
<ul class="profile-courses">
{% for course in courses|slice:":6" %}
<li>
<a href="{% url 'courses:show' course_id=course.id course_name=course.slug %}">
<img src="/{{ course.thumbnail }}" alt="{{ course.name }}" class="course-thumb-mini">
<span>{{ course.name }}</span>
</a>
</li>
{% endfor %}
</ul>
<div class="text-right">
<a class="btn btn-secondary btn-sm" href="{% url 'user_courses' %}">Voir tous mes cours</a>
</div>
{% else %}
<p class="muted">Aucun cours publié pour le moment.</p>
{% endif %}
{% endwith %}
</div>
<div class="profile-card">
<h3>Activité</h3>
<p class="muted">Le fil dactivité arrive bientôt.</p>
</div>
</div>
</div>
</section>
{% endblock %}

View file

@ -1,11 +1,39 @@
{% extends 'layout.html' %}
{% block content %}
<section>
<h2>Update Profile</h2>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ profile_form.as_p }}
<button type="submit">Modifier</button>
</form>
<section class="profile-section">
{% include "../partials/_profile_nav.html" %}
<div class="profile-content">
<div class="profile-grid">
<div class="profile-card">
<h3>Modifier mon profil public</h3>
<div style="display:flex; align-items:center; gap:16px; margin: 10px 0 14px;">
<img src="/{{ user.profile.avatar }}" alt="Avatar actuel" class="profile-avatar" style="width:72px;height:72px;margin-top:0;">
<div>
<div class="muted">Aperçu de lavatar actuel</div>
<small class="muted">Vous pouvez téléverser une nouvelle image cidessous.</small>
</div>
</div>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ profile_form.as_p }}
<div class="text-right" style="margin-top:12px; display:flex; gap:8px; justify-content:flex-end;">
<a href="{% url 'profile' %}" class="btn btn-secondary">Annuler</a>
<button type="submit" class="btn btn-primary">Enregistrer</button>
</div>
</form>
</div>
<div class="profile-card">
<h3>Conseils</h3>
<ul class="profile-about-list">
<li>Utilisez votre vrai nom pour être plus facilement reconnu.</li>
<li>Une bio claire aide la communauté à mieux vous connaître.</li>
<li>Privilégiez une photo carrée pour un meilleur rendu.</li>
</ul>
</div>
</div>
</div>
</section>
{% endblock %}