| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209 |
- """
- Helpers SEO pour le blog Django
- Génération automatique de métadonnées structurées et optimisées
- """
- from django.utils.html import strip_tags
- from django.utils.text import Truncator
- from django.urls import reverse
- class SEOMetadata:
- """Classe pour gérer les métadonnées SEO d'une page"""
-
- def __init__(self, request, obj=None):
- self.request = request
- self.obj = obj
- self.site_name = "Mr Duhaz"
- self.site_url = "https://www.duhaz.fr"
- # Image par défaut optimisée pour Open Graph (1200x630px recommandé)
- self.default_image = f"{self.site_url}/static/logo-txt-Mrduhaz.png"
- self.default_image_alt = "Logo Mr Duhaz - Blog de développement web"
- self.twitter_handle = "@mrduhaz" # Remplacez par votre vrai handle Twitter
-
- def get_absolute_url(self, path=''):
- """Génère une URL absolue"""
- if path:
- return f"{self.site_url}{path}"
- return self.request.build_absolute_uri()
-
- def get_image_url(self, image_field):
- """
- Génère une URL absolue pour une image
- Vérifie si l'URL est déjà absolue ou relative
- """
- if not image_field:
- return self.default_image
-
- # Si l'image commence par http/https, c'est déjà une URL absolue
- if image_field.startswith(('http://', 'https://')):
- return image_field
-
- # Sinon, construire l'URL absolue
- if image_field.startswith('/'):
- return f"{self.site_url}{image_field}"
- else:
- return f"{self.site_url}/{image_field}"
-
- def clean_description(self, text, max_length=160):
- """Nettoie et tronque une description pour le SEO"""
- if not text:
- return ""
- # Supprime les balises HTML
- clean_text = strip_tags(text)
- # Tronque intelligemment
- return Truncator(clean_text).chars(max_length, truncate='...')
-
- def get_blog_metadata(self, article):
- """Génère les métadonnées complètes pour un article de blog"""
- if not article:
- return self.get_default_metadata()
-
- # URL absolue de l'article
- article_url = self.get_absolute_url(
- reverse('blog_play', args=[article.b_titre_slugify])
- )
-
- # Image de l'article (ou image par défaut)
- image_url = self.get_image_url(article.b_description_img)
- image_alt = f"Image de l'article : {article.b_titre}"
-
- # Description nettoyée
- description = self.clean_description(article.b_description, 160)
-
- # Catégories pour les keywords
- categories = [cat.cb_titre for cat in article.b_cat.all()]
- keywords = article.b_mots_clefs if article.b_mots_clefs else ', '.join(categories)
-
- # Dates de publication et modification au format ISO 8601
- published_time = article.b_publdate.isoformat() if article.b_publdate else None
- # Pour l'instant, on utilise la même date pour modified_time
- # Plus tard, vous pourrez ajouter un champ b_modifdate dans votre modèle
- modified_time = article.b_publdate.isoformat() if article.b_publdate else None
-
- metadata = {
- # Basiques
- 'title': f"{article.b_titre} | {self.site_name}",
- 'description': description,
- 'keywords': keywords,
- 'canonical_url': article_url,
- 'image': image_url,
- 'image_alt': image_alt,
-
- # Open Graph (Facebook)
- 'og': {
- 'type': 'article',
- 'title': article.b_titre,
- 'description': description,
- 'url': article_url,
- 'image': image_url,
- 'image:alt': image_alt,
- 'image:width': '1200',
- 'image:height': '630',
- 'site_name': self.site_name,
- 'locale': 'fr_FR',
- 'article:published_time': published_time,
- 'article:modified_time': modified_time,
- 'article:author': 'Mr Duhaz',
- 'article:section': categories[0] if categories else 'Blog',
- 'article:tag': categories,
- },
-
- # Twitter Cards
- 'twitter': {
- 'card': 'summary_large_image',
- 'title': article.b_titre,
- 'description': description,
- 'image': image_url,
- 'image:alt': image_alt,
- 'site': self.twitter_handle,
- 'creator': self.twitter_handle,
- },
-
- # Schema.org JSON-LD
- 'schema': self.get_article_schema(article, article_url, image_url, description, modified_time),
- }
-
- return metadata
-
- def get_article_schema(self, article, url, image, description, modified_time=None):
- """Génère le schema JSON-LD pour un article"""
- categories = [cat.cb_titre for cat in article.b_cat.all()]
-
- published_time = article.b_publdate.isoformat() if article.b_publdate else None
-
- schema = {
- "@context": "https://schema.org",
- "@type": "BlogPosting",
- "headline": article.b_titre,
- "description": description,
- "image": {
- "@type": "ImageObject",
- "url": image,
- "width": 1200,
- "height": 630
- },
- "author": {
- "@type": "Person",
- "name": "Mr Duhaz",
- "url": self.site_url
- },
- "publisher": {
- "@type": "Organization",
- "name": self.site_name,
- "logo": {
- "@type": "ImageObject",
- "url": f"{self.site_url}/static/logo-txt-Mrduhaz.png"
- }
- },
- "url": url,
- "datePublished": published_time,
- "dateModified": modified_time or published_time,
- "articleSection": categories[0] if categories else "Blog",
- "keywords": article.b_mots_clefs if article.b_mots_clefs else ', '.join(categories),
- "wordCount": len(strip_tags(article.b_contenu).split()),
- "articleBody": self.clean_description(article.b_contenu, 500),
- }
-
- return schema
-
- def get_listing_metadata(self, category=None):
- """Génère les métadonnées pour la page de listing"""
- if category:
- title = f"Articles dans {category.cb_titre} | {self.site_name}"
- description = f"Découvrez tous les articles de la catégorie {category.cb_titre} sur le blog de {self.site_name}"
- url = self.get_absolute_url(reverse('blog_tag', args=[category.cb_titre_slgify]))
- else:
- title = f"Blog | {self.site_name}"
- description = f"Découvrez tous les articles du blog de {self.site_name} : tutoriels, actualités et guides"
- url = self.get_absolute_url(reverse('blog_index'))
-
- metadata = {
- 'title': title,
- 'description': description,
- 'canonical_url': url,
- 'og': {
- 'type': 'website',
- 'title': title,
- 'description': description,
- 'url': url,
- 'image': f"{self.site_url}/static/logo-txt-Mrduhaz.png",
- 'site_name': self.site_name,
- 'locale': 'fr_FR',
- },
- 'twitter': {
- 'card': 'summary',
- 'title': title,
- 'description': description,
- 'image': f"{self.site_url}/static/logo-txt-Mrduhaz.png",
- },
- }
-
- return metadata
-
- def get_default_metadata(self):
- """Métadonnées par défaut du site"""
- return {
- 'title': self.site_name,
- 'description': "Blog de Mr Duhaz : développement web, tutoriels et astuces",
- 'canonical_url': self.site_url,
- }
|