Développement d’API et de pages de cours pour le projet Luffy (backend et frontend)

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 pagination
  • changer_taille_page(val) et changer_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">&euro;{{ 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">&euro;{{ 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>

Étiquettes: Django DRF Vue.js API REST Pagination

Publié le 21 juin à 05h39