Conception et implémentation d'un moteur de blog avec Django

L'architecture d'une plateforme de blogging type "BlogCMS" repose sur le framwork Django. Ce système s'articule autour d'une structure de données robuste permettant de gérer des comptes utilisateurs, des espaces personnels, des publications et des interactions sociales (commentaires et votes).

Structure de la base de données (Modèles)

Dans Django, chaque table de la base de données est représentée par une classe Python. Pour un système complet, nous définissons des modèles gérant les profils, le contenu et les métadonnées.


from django.db import models
from django.contrib.auth.models import AbstractUser

class ProfilUtilisateur(AbstractUser):
   """Extension du modèle utilisateur par défaut"""
   id_unique = models.BigAutoField(primary_key=True)
   alias = models.CharField(verbose_name='Pseudonyme', max_length=32)
   mobile = models.CharField(max_length=11, blank=True, null=True, unique=True)
   photo_profil = models.FileField(verbose_name='Avatar', upload_to='photos', default="/photos/defaut.jpg")
   date_inscription = models.DateTimeField(auto_now_add=True)

   def __str__(self):
       return self.username

class EspaceBlog(models.Model):
   """Chaque utilisateur possède son propre espace de blog personnalisé"""
   uid = models.BigAutoField(primary_key=True)
   nom_site = models.CharField(max_length=64)
   suffixe_url = models.CharField(max_length=32, unique=True)
   style_theme = models.CharField(max_length=32)
   proprietaire = models.OneToOneField(to='ProfilUtilisateur', on_delete=models.CASCADE)

   def __str__(self):
       return self.nom_site

class Billet(models.Model):
   """Représente un article publié sur un blog"""
   id_billet = models.BigAutoField(primary_key=True)
   titre = models.CharField(max_length=100)
   resume = models.CharField(max_length=255)
   nb_vues = models.IntegerField(default=0)
   nb_commentaires = models.IntegerField(default=0)
   likes = models.IntegerField(default=0)
   dislikes = models.IntegerField(default=0)
   date_publication = models.DateTimeField(auto_now_add=True)
   
   rubrique = models.ForeignKey(to='Rubrique', null=True, on_delete=models.SET_NULL)
   auteur = models.ForeignKey(to='ProfilUtilisateur', on_delete=models.CASCADE)
   etiquettes = models.ManyToManyField(
       to="Etiquette",
       through='BilletVersEtiquette',
       through_fields=('billet', 'etiquette'),
   )

class ContenuBillet(models.Model):
   """Stocke le contenu volumineux séparément pour optimiser les performances"""
   corps = models.TextField()
   billet_lie = models.OneToOneField(to='Billet', on_delete=models.CASCADE)

class Rubrique(models.Model):
   """Catégories personnalisées créées par le blogueur"""
   nom = models.CharField(max_length=32)
   blog_parent = models.ForeignKey(to='EspaceBlog', on_delete=models.CASCADE)

   def __str__(self):
       return self.nom

class Etiquette(models.Model):
   """Tags associés aux articles"""
   nom = models.CharField(max_length=32)
   blog_parent = models.ForeignKey(to='EspaceBlog', on_delete=models.CASCADE)

class BilletVersEtiquette(models.Model):
   """Table de liaison pour la relation ManyToMany entre articles et tags"""
   billet = models.ForeignKey(to="Billet", on_delete=models.CASCADE)
   etiquette = models.ForeignKey(to="Etiquette", on_delete=models.CASCADE)

   class Meta:
       unique_together = [('billet', 'etiquette')]

class InteractionCommentaire(models.Model):
   """Gestion des commentaires avec support de l'arborescence (réponses)"""
   id_com = models.BigAutoField(primary_key=True)
   texte = models.CharField(max_length=255)
   date_post = models.DateTimeField(auto_now_add=True)
   lecteur = models.ForeignKey(to='ProfilUtilisateur', on_delete=models.CASCADE)
   billet_concerne = models.ForeignKey(to='Billet', on_delete=models.CASCADE)
   parent = models.ForeignKey('self', blank=True, null=True, on_delete=models.CASCADE)

class VoteBillet(models.Model):
   """Enregistre les votes positifs/négatifs des utilisateurs"""
   utilisateur = models.ForeignKey('ProfilUtilisateur', on_delete=models.CASCADE)
   billet = models.ForeignKey("Billet", on_delete=models.CASCADE)
   est_positif = models.BooleanField(default=True)
   

Configuration du routage (URLs)

Le routage est divisé en deux segments : les routes globales du portail et les routes spécifiques à l'espace personnel de chaque utilisateur via une inclusion (routing distribué).


# urls_globales.py
from django.conf.urls import url, include
from blog_app import views

urlpatterns = [
   url(r'^inscription/$', views.inscription_compte),
   url(r'^connexion/$', views.authentification),
   url(r'^deconnexion/$', views.fermeture_session),
   url(r'^captcha/$', views.generer_captcha),
   url(r'^accueil/$', views.page_accueil),
   url(r'^$', views.page_accueil),
   url(r'^espace/', include('blog_app.urls_personnelles')),
]
   

# urls_personnelles.py
from django.conf.urls import url
from blog_app import views

urlpatterns = [
   url(r'^gestion/$', views.tableau_bord),
   url(r'^nouveau-billet/$', views.rediger_article),
   url(r'^(?P<user_slug>.*)/billet/(?P<id_billet>\d+)/$', views.detail_article),
   url(r'^(?P<user_slug>.*)/$', views.index_blog_prive, name="lien_blog"),
]
   

Gestion des fichiers média et fichiers statiques

Dans Django, il est crucial de distinguer les fichiers statiques (CSS, JS fournis par le développeur) et les fichiers médias (images envoyées par les utilisateurs). Voici comment configurer ces accès dans le fichier settings.py.


import os

# Fichiers statiques (Développement)
STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'statique_global')]

# Fichiers médias (Uploads utilisateurs)
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, "stockage_media")

# Définition du modèle utilisateur personnalisé
AUTH_USER_MODEL = "blog_app.ProfilUtilisateur"
   

Interface d'inscription avec validation (Formularies)

L'utilisation de forms.Form permet de gérer la validation des données côté serveur (unicité du nom, force du mot de passe) avant l'enregistrement en base.


from django import forms
from django.core.exceptions import ValidationError
from .models import ProfilUtilisateur

class FormulaireInscription(forms.Form):
   nom_usage = forms.CharField(
       min_length=4, 
       label="Identifiant",
       widget=forms.TextInput(attrs={"class": "form-control"})
   )
   code_secret = forms.CharField(
       min_length=8,
       widget=forms.PasswordInput(attrs={"class": "form-control"})
   )
   confirmation = forms.CharField(
       widget=forms.PasswordInput(attrs={"class": "form-control"})
   )

   def clean_nom_usage(self):
       data = self.cleaned_data.get("nom_usage")
       if ProfilUtilisateur.objects.filter(username=data).exists():
           raise ValidationError("Cet identifiant est déjà utilisé.")
       return data

   def clean(self):
       pw1 = self.cleaned_data.get("code_secret")
       pw2 = self.cleaned_data.get("confirmation")
       if pw1 != pw2:
           raise ValidationError("Les mots de passe ne correspondent pas.")
       return self.cleaned_data
   

Logique de la vue d'accueil

La vue principale doit être capable de filtrer les articles par catégorie ou d'afficher les derniers billets publiés de manière globale.


def page_accueil(request, slug_categorie=None):
   if slug_categorie:
       liste_billets = Billet.objects.filter(rubrique__nom=slug_categorie)
   else:
       liste_billets = Billet.objects.all().order_by('-date_publication')
       
   categories_site = Rubrique.objects.all()
   return render(request, 'accueil.html', {
       'billets': liste_billets, 
       'categories': categories_site
   })
   

Système d'interactions via Ajax

Pour le système de "Like" ou l'ajout de commentaires sans recharger la page, l'utilisation de requêtes Ajax est privilégiée.


// Exemple de script pour le vote (jQuery)
$('.btn-vote').click(function () {
   const articleId = $(this).data('id');
   const typeVote = $(this).data('type'); // 'plus' ou 'moins'

   $.ajax({
       url: '/voter/',
       method: 'POST',
       data: {
           'id': articleId,
           'action': typeVote,
           'csrfmiddlewaretoken': $('[name=csrfmiddlewaretoken]').val()
       },
       success: function (response) {
           if (response.success) {
               // Mise à jour dynamique du compteur
               $('#score-' + articleId).text(response.nouveau_score);
           } else {
               alert(response.message);
           }
       }
   });
});
   

Enfin, l'interface d'administration personnalisée permet aux utilisateurs de gérer leurs propres articles en utilisant des éditeurs de texte riche comme KindEditor ou TinyMCE, intégrés via des scripts JavaScript dans les templates Django.

Étiquettes: Django Python web-development ORM backend

Publié le 16 août à 12h58