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.