1
0

seo_helpers.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. """
  2. Helpers SEO pour le blog Django
  3. Génération automatique de métadonnées structurées et optimisées
  4. """
  5. from django.utils.html import strip_tags
  6. from django.utils.text import Truncator
  7. from django.urls import reverse
  8. class SEOMetadata:
  9. """Classe pour gérer les métadonnées SEO d'une page"""
  10. def __init__(self, request, obj=None):
  11. self.request = request
  12. self.obj = obj
  13. self.site_name = "Mr Duhaz"
  14. self.site_url = "https://www.duhaz.fr"
  15. def get_absolute_url(self, path=''):
  16. """Génère une URL absolue"""
  17. if path:
  18. return f"{self.site_url}{path}"
  19. return self.request.build_absolute_uri()
  20. def clean_description(self, text, max_length=160):
  21. """Nettoie et tronque une description pour le SEO"""
  22. if not text:
  23. return ""
  24. # Supprime les balises HTML
  25. clean_text = strip_tags(text)
  26. # Tronque intelligemment
  27. return Truncator(clean_text).chars(max_length, truncate='...')
  28. def get_blog_metadata(self, article):
  29. """Génère les métadonnées complètes pour un article de blog"""
  30. if not article:
  31. return self.get_default_metadata()
  32. # URL absolue de l'article
  33. article_url = self.get_absolute_url(
  34. reverse('blog_play', args=[article.b_titre_slugify])
  35. )
  36. # Image de l'article (ou image par défaut)
  37. image_url = article.b_description_img if article.b_description_img else \
  38. f"{self.site_url}/static/logo-txt-Mrduhaz.png"
  39. # Description nettoyée
  40. description = self.clean_description(article.b_description, 160)
  41. # Catégories pour les keywords
  42. categories = [cat.cb_titre for cat in article.b_cat.all()]
  43. keywords = article.b_mots_clefs if article.b_mots_clefs else ', '.join(categories)
  44. # Date de publication au format ISO 8601
  45. published_time = article.b_publdate.isoformat() if article.b_publdate else None
  46. metadata = {
  47. # Basiques
  48. 'title': f"{article.b_titre} | {self.site_name}",
  49. 'description': description,
  50. 'keywords': keywords,
  51. 'canonical_url': article_url,
  52. 'image': image_url,
  53. # Open Graph (Facebook)
  54. 'og': {
  55. 'type': 'article',
  56. 'title': article.b_titre,
  57. 'description': description,
  58. 'url': article_url,
  59. 'image': image_url,
  60. 'site_name': self.site_name,
  61. 'locale': 'fr_FR',
  62. 'article:published_time': published_time,
  63. 'article:author': 'Mr Duhaz',
  64. 'article:section': categories[0] if categories else 'Blog',
  65. 'article:tag': categories,
  66. },
  67. # Twitter Cards
  68. 'twitter': {
  69. 'card': 'summary_large_image',
  70. 'title': article.b_titre,
  71. 'description': description,
  72. 'image': image_url,
  73. 'site': '@mrduhaz', # Remplacer par votre handle Twitter
  74. },
  75. # Schema.org JSON-LD
  76. 'schema': self.get_article_schema(article, article_url, image_url, description),
  77. }
  78. return metadata
  79. def get_article_schema(self, article, url, image, description):
  80. """Génère le schema JSON-LD pour un article"""
  81. categories = [cat.cb_titre for cat in article.b_cat.all()]
  82. schema = {
  83. "@context": "https://schema.org",
  84. "@type": "BlogPosting",
  85. "headline": article.b_titre,
  86. "description": description,
  87. "image": image,
  88. "author": {
  89. "@type": "Person",
  90. "name": "Mr Duhaz",
  91. "url": self.site_url
  92. },
  93. "publisher": {
  94. "@type": "Organization",
  95. "name": self.site_name,
  96. "logo": {
  97. "@type": "ImageObject",
  98. "url": f"{self.site_url}/static/logo-txt-Mrduhaz.png"
  99. }
  100. },
  101. "url": url,
  102. "datePublished": article.b_publdate.isoformat() if article.b_publdate else None,
  103. "dateModified": article.b_publdate.isoformat() if article.b_publdate else None,
  104. "articleSection": categories[0] if categories else "Blog",
  105. "keywords": article.b_mots_clefs if article.b_mots_clefs else ', '.join(categories),
  106. "wordCount": len(strip_tags(article.b_contenu).split()),
  107. "articleBody": self.clean_description(article.b_contenu, 500),
  108. }
  109. return schema
  110. def get_listing_metadata(self, category=None):
  111. """Génère les métadonnées pour la page de listing"""
  112. if category:
  113. title = f"Articles dans {category.cb_titre} | {self.site_name}"
  114. description = f"Découvrez tous les articles de la catégorie {category.cb_titre} sur le blog de {self.site_name}"
  115. url = self.get_absolute_url(reverse('blog_tag', args=[category.cb_titre_slgify]))
  116. else:
  117. title = f"Blog | {self.site_name}"
  118. description = f"Découvrez tous les articles du blog de {self.site_name} : tutoriels, actualités et guides"
  119. url = self.get_absolute_url(reverse('blog_index'))
  120. metadata = {
  121. 'title': title,
  122. 'description': description,
  123. 'canonical_url': url,
  124. 'og': {
  125. 'type': 'website',
  126. 'title': title,
  127. 'description': description,
  128. 'url': url,
  129. 'image': f"{self.site_url}/static/logo-txt-Mrduhaz.png",
  130. 'site_name': self.site_name,
  131. 'locale': 'fr_FR',
  132. },
  133. 'twitter': {
  134. 'card': 'summary',
  135. 'title': title,
  136. 'description': description,
  137. 'image': f"{self.site_url}/static/logo-txt-Mrduhaz.png",
  138. },
  139. }
  140. return metadata
  141. def get_default_metadata(self):
  142. """Métadonnées par défaut du site"""
  143. return {
  144. 'title': self.site_name,
  145. 'description': "Blog de Mr Duhaz : développement web, tutoriels et astuces",
  146. 'canonical_url': self.site_url,
  147. }