Projet Luffy – Partie Cours
Backend : application Cours
1. Insertion des données initialse
-- Table enseignant
INSERT INTO enseignant (id, ordre, visible, supprime, date_creation, date_maj, nom, role, titre, signature, image, description)
VALUES (1,1,1,0,'2022-07-14 13:44:19.661327','2022-07-14 13:46:54.246271','Alex',1,'Directeur pédagogique Python','Roi d''or', 'teacher/alex_icon.png', 'CTO & co-fondateur, formateur Python reconnu');
-- Table catégorie
INSERT INTO categorie_cours (id, ordre, visible, supprime, date_creation, date_maj, nom)
VALUES (1,1,1,0,'2022-07-14 13:40:58.690413','2022-07-14 13:40:58.690477','Python');
-- Table cours
INSERT INTO cours (id, ordre, visible, supprime, date_creation, date_maj, nom, image, type, description, niveau, date_publication, duree, chemin_piece_jointe, statut, etudiants, total_sections, sections_publiees, prix, categorie_id, enseignant_id)
VALUES (1,1,1,0,'2022-07-14 13:54:33.095201','2022-07-14 13:54:33.095238','Python 21 jours pour débuter','courses/alex_python.png',0,'Description du cours',0,'2022-07-14',21,'',0,231,120,120,0.00,1,1);
-- Table chapitre
INSERT INTO chapitre_cours (id, ordre, visible, supprime, date_creation, date_maj, chapitre, nom, resume, date_publication, cours_id)
VALUES (1,1,1,0,'2022-07-14 13:58:34.867005','2022-07-14 14:00:58.276541',1,'Fondamentaux informatiques','','2022-07-14',1);
-- Table section (leçon)
INSERT INTO section_cours (id, visible, supprime, date_creation, date_maj, nom, ordre, type_section, lien_section, duree, date_publication, essai_gratuit, chapitre_id)
VALUES (1,1,0,'2022-07-14 14:02:33.779098','2022-07-14 14:02:33.779135','Informatique fondamentale (partie 1)',1,2,NULL,NULL,'2022-07-14 14:02:33.779193',1,1);
2. API catégories de cours
2.1 Vue
from rest_framework.viewsets import GenericViewSet
from .models import CategorieCours
from .serializers import CategorieCoursSerializer
from utils.vue_commune import VueListeBase
class VueCategorieCours(GenericViewSet, VueListeBase):
jeu_requetes = CategorieCours.objects.filter(supprime=False, visible=True).order_by('ordre')
serializer_class = CategorieCoursSerializer
2.2 Classe de base personnalisée
from rest_framework.mixins import ListModelMixin
from utils.reponse_commune import ReponseAPI
class VueListeBase(ListModelMixin):
def list(self, requete, *args, **kwargs):
res = super().list(requete, *args, **kwargs)
return ReponseAPI(resultat=res.data)
2.3 Sérialiseur
from rest_framework import serializers
from .models import CategorieCours
class CategorieCoursSerializer(serializers.ModelSerializer):
class Meta:
model = CategorieCours
champs = ['id', 'nom']
2.4 Routage
from rest_framework.routers import SimpleRouter
from cours import vues
routeur = SimpleRouter()
routeur.register('categorie', vues.VueCategorieCours, 'categorie')
urlpatterns = []
urlpatterns += routeur.urls
3. API liste des cours
Fonctionnalités : filtrage (par catégorie), tri (par nombre d'étudiants ou prix), pagination. Retourne également les informations de l'enseignant, et jusqu'à 4 sections (leçons) par cours.
3.1 Routage
routeur.register('cours', vues.VueCours, 'cours')
3.2 Modèle
class Cours( ModeleDeBase):
def nom_niveau(self):
return self.get_niveau_display()
def nom_statut(self):
return self.get_statut_display()
def nom_type_cours(self):
return self.get_type_cours_display()
def liste_sections(self):
liste = []
chapitres = self.chapitres_cours.all()
for chap in chapitres:
sections = chap.sections_cours.all()
for sec in sections:
liste.append({
'nom': sec.nom,
'lien': sec.lien_section,
'duree': sec.duree,
'essai_gratuit': sec.essai_gratuit,
})
if len(liste) >= 4:
return liste
return liste
class Enseignant(ModeleDeBase):
def nom_role(self):
return self.get_role_display()
3.3 Sérialiseur
class EnseignantSerializer(serializers.ModelSerializer):
class Meta:
model = Enseignant
champs = ['id', 'nom', 'nom_role', 'titre', 'signature', 'image', 'description']
class CoursSerializer(serializers.ModelSerializer):
enseignant = EnseignantSerializer()
class Meta:
model = Cours
champs = [
'id', 'nom', 'image', 'description', 'chemin_piece_jointe',
'sections_publiees', 'prix', 'etudiants', 'duree', 'total_sections',
'nom_type_cours', 'nom_niveau', 'nom_statut',
'enseignant', 'liste_sections'
]
3.4 Filtrage, pagination, tri
Filtrage avec django-filter :
pip install django-filter
# dans la vue :
filtre_arriere_plans = [FiltreBackendDjango]
champs_filtres = ['categorie_cours']
Tri avec OrderingFilter :
filtre_arriere_plans = [FiltreTri]
champs_tri = ['prix', 'etudiants']
Pagination personnalisée :
from rest_framework.pagination import PageNumberPagination
class PaginationPageNombre(PageNumberPagination):
taille_page = 2
param_page = 'page'
param_taille_page = 'taille_page'
max_taille_page = 5
4. API détail d’un cours
# Dans VueCours (GenericViewSet)
def retrieve(self, requete, *args, **kwargs):
return ReponseAPI(data=super().retrieve(requete, *args, **kwargs).data)
5. API chapitres d’un cours
5.1 Vue
class VueChapitreCours(GenericViewSet, VueListeBase):
jeu_requetes = ChapitreCours.objects.filter(supprime=False, visible=True).order_by('ordre')
serializer_class = ChapitreCoursSerializer
filtre_arriere_plans = [FiltreBackendDjango]
champs_filtres = ['cours']
5.2 Sérialiseur
class SectionCoursSerializer(serializers.ModelSerializer):
class Meta:
model = SectionCours
champs = ('id', 'nom', 'ordre', 'lien_section', 'duree', 'essai_gratuit')
class ChapitreCoursSerializer(serializers.ModelSerializer):
sections_cours = SectionCoursSerializer(many=True)
class Meta:
model = ChapitreCours
champs = ('id', 'nom', 'chapitre', 'resume', 'sections_cours')
5.3 Routage
routeur.register('chapitre', vues.VueChapitreCours, 'chapitre')
Frontend : pages Cours
1. Liste des cours (page pirncipale)
Composant Vue utilisant Header et Footer communs. Gère la sélection de catégorie, le tri, et la pagination via Element UI.
Données :
data() {
return {
liste_categories: [],
liste_cours: [],
total_cours: 0,
filtres: { categorie_cours: 0, tri: '-id', taille_page: 2, page: 1 }
}
}
Méthodes :
obtenir_categories()– GET/cours/categorie/obtenir_cours()– GET/cours/cours/avec paramètres de filtrage, tri et paginationchanger_taille_page(val)etchanger_page(val)pour la pagination
Template (extrait) :
<div class="cours">
<Header />
<div class="principal">
<!-- Filtres -->
<div class="filtres">
<ul class="liste-categories">
<li class="titre">Catégories :</li>
<li :class="filtres.categorie_cours==0?'actif':''" @click="filtres.categorie_cours=0">Tous</li>
<li v-for="cat in liste_categories" :key="cat.id"
:class="filtres.categorie_cours==cat.id?'actif':''"
@click="filtres.categorie_cours=cat.id">{{ cat.nom }}</li>
</ul>
<!-- Tri -->
<div class="tri">
<ul>
<li :class="filtres.tri.includes('id')?'actif':''" @click="filtres.tri='-id'">Défaut</li>
<li :class="filtres.tri.includes('etudiants')?'actif':''"
@click="filtres.tri=filtres.tri=='-etudiants'?'etudiants':'-etudiants'">Popularité</li>
<li :class="{'actif':filtres.tri.includes('prix'), 'prix-montant':filtres.tri=='-prix'}"
@click="filtres.tri=filtres.tri=='-prix'?'prix':'-prix'">Prix</li>
</ul>
<p>Total : {{ total_cours }} cours</p>
</div>
</div>
<!-- Liste des cours -->
<div class="liste-cours">
<div class="element-cours" v-for="c in liste_cours" :key="c.id">
<div class="image"><img :src="c.image" /></div>
<div class="infos">
<h3><router-link :to="'/detail/'+c.id">{{ c.nom }}</router-link>
<span>{{ c.etudiants }} inscrits</span></h3>
<p>{{ c.enseignant.nom }} {{ c.enseignant.titre }}</p>
<ul><li v-for="s in c.liste_sections" :key="s.nom">{{ s.nom }}</li></ul>
<div class="prix">€{{ c.prix }}</div>
</div>
</div>
</div>
<el-pagination ... />
</div>
<Footer />
</div>
2. Détail du cours
Installation du lecteur vidéo
npm install vue-core-video-player -S
// Dans main.js
import VueCoreVideoPlayer from 'vue-core-video-player'
Vue.use(VueCoreVideoPlayer, { lang: 'fr-FR' })
Composant Détail
Charge les informations du cours (GET /cours/cours/{id}/) et la liste des chapitres (GET /cours/chapitre/?cours={id}). Affiche un lecteur vidéo, les onglets (description, chapitres, commentaires, FAQ).
Template (extrait) :
<div class="detail">
<Header />
<div class="conteneur">
<div class="lecteur">
<vue-core-video-player src="https://..." controls="auto" />
</div>
<div class="infos">
<h3>{{ infos_cours.nom }}</h3>
<p>{{ infos_cours.etudiants }} étudiants</p>
<div class="prix">€{{ infos_cours.prix }}</div>
</div>
</div>
<!-- Onglets -->
<div class="onglets">
<ul>
<li @click="onglet=1" :class="onglet==1?'actif':''">Description</li>
<li @click="onglet=2" :class="onglet==2?'actif':''">Chapitres</li>
</ul>
</div>
<div class="contenu-onglet" v-if="onglet==2">
<div v-for="chap in chapitres" :key="chap.id">
<p>Chapitre {{ chap.chapitre }} : {{ chap.nom }}</p>
<ul>
<li v-for="sec in chap.sections_cours" :key="sec.id">
{{ sec.nom }} <span v-if="sec.essai_gratuit">(Essai gratuit)</span>
</li>
</ul>
</div>
</div>
<Footer />
</div>