소스 검색

correct linkedin

Laurent Hazart 1 개월 전
부모
커밋
72b3601ae3
5개의 변경된 파일468개의 추가작업 그리고 0개의 파일을 삭제
  1. 268 0
      RESOLUTION_LINKEDIN.md
  2. 1 0
      blog/seo_helpers.py
  3. 12 0
      blog/templates/blog/seo_meta.html
  4. 1 0
      blog/templates/read.html
  5. 186 0
      test_og_meta.py

+ 268 - 0
RESOLUTION_LINKEDIN.md

@@ -0,0 +1,268 @@
+# 🔍 Résolution du problème de partage LinkedIn
+
+## 🐛 Problème
+Le partage LinkedIn ne montre pas l'image de l'article, alors que Facebook fonctionne correctement.
+
+---
+
+## 📋 Exigences LinkedIn pour Open Graph
+
+LinkedIn est **très strict** sur les métadonnées Open Graph :
+
+### 1️⃣ **Taille de l'image**
+- **Minimum** : 1200 x 627 pixels
+- **Recommandé** : 1200 x 630 pixels
+- **Ratio** : 1.91:1 (format paysage)
+- **Poids** : Maximum 5 MB
+- **Format** : JPG ou PNG
+
+### 2️⃣ **URL absolue requise**
+```html
+<!-- ❌ NE FONCTIONNE PAS -->
+<meta property="og:image" content="/static/image.jpg">
+
+<!-- ✅ FONCTIONNE -->
+<meta property="og:image" content="https://www.duhaz.fr/static/image.jpg">
+```
+
+### 3️⃣ **Balises obligatoires**
+```html
+<meta property="og:title" content="Titre de l'article">
+<meta property="og:description" content="Description...">
+<meta property="og:image" content="https://www.duhaz.fr/image.jpg">
+<meta property="og:url" content="https://www.duhaz.fr/article">
+<meta property="og:type" content="article">
+```
+
+### 4️⃣ **Balises recommandées pour l'image**
+```html
+<meta property="og:image:width" content="1200">
+<meta property="og:image:height" content="630">
+<meta property="og:image:alt" content="Description de l'image">
+<meta property="og:image:type" content="image/jpeg">
+```
+
+---
+
+## 🔍 Vérification de votre configuration actuelle
+
+### Étape 1 : Vérifier les métadonnées générées
+
+1. Démarrez votre serveur :
+```bash
+cd /Users/duhaz/projets/blog-duhaz
+./start.sh
+```
+
+2. Ouvrez un article dans votre navigateur
+
+3. Faites **clic droit → Afficher le code source**
+
+4. Recherchez les balises `<meta property="og:` et vérifiez :
+   - ✅ `og:image` contient une URL **complète** commençant par `https://`
+   - ✅ L'image fait au moins 1200x627 pixels
+   - ✅ Toutes les balises sont présentes
+
+### Étape 2 : Tester avec l'outil LinkedIn
+
+LinkedIn fournit un outil de débogage :
+**https://www.linkedin.com/post-inspector/**
+
+1. Collez l'URL de votre article
+2. Cliquez sur "Inspect"
+3. LinkedIn vous indiquera les problèmes détectés
+
+---
+
+## 🛠️ Solutions aux problèmes courants
+
+### Problème 1 : Image trop petite
+**Symptôme** : LinkedIn affiche un texte mais pas d'image
+
+**Solution** : Assurez-vous que vos images font au moins 1200x630 pixels
+
+Dans votre admin Django, lors de l'ajout d'une image (`b_description_img`), utilisez des images qui respectent ces dimensions.
+
+### Problème 2 : URL relative au lieu d'absolue
+**Symptôme** : L'image ne s'affiche nulle part
+
+**Vérification dans le code** :
+```python
+# Dans /blog/seo_helpers.py, ligne ~40
+def get_image_url(self, image_field):
+    if not image_field:
+        return self.default_image
+    
+    # Vérifie si l'URL est déjà 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}"
+```
+
+**✅ Votre code est correct**, il transforme déjà les URLs relatives en absolues.
+
+### Problème 3 : Cache de LinkedIn
+**Symptôme** : Vous avez corrigé mais LinkedIn montre toujours l'ancienne version
+
+**Solution** : LinkedIn met en cache les métadonnées. Utilisez l'outil Post Inspector pour forcer la mise à jour :
+1. Allez sur https://www.linkedin.com/post-inspector/
+2. Entrez votre URL
+3. Cliquez sur "Inspect"
+4. LinkedIn rafraîchira les métadonnées
+
+---
+
+## ✅ Amélioration recommandée : Ajouter og:image:type
+
+Actuellement, votre code ne spécifie pas le type MIME de l'image. Ajoutons-le :
+
+### Modification dans `/blog/seo_helpers.py`
+
+Ligne ~85, dans la fonction `get_blog_metadata()`, ajoutez :
+
+```python
+# 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',
+    'image_type': 'image/jpeg',  # ← AJOUTER CETTE LIGNE
+    'site_name': self.site_name,
+    'locale': 'fr_FR',
+    # ... reste du code
+},
+```
+
+### Modification dans `/blog/templates/read.html`
+
+Ligne ~27, ajoutez :
+
+```django
+<meta property="og:image" content="{{ page.seo.og.image }}">
+<meta property="og:image:alt" content="{{ page.seo.image_alt }}">
+<meta property="og:image:width" content="{{ page.seo.og.image_width }}">
+<meta property="og:image:height" content="{{ page.seo.og.image_height }}">
+<meta property="og:image:type" content="{{ page.seo.og.image_type }}">  <!-- AJOUTER -->
+<meta property="og:site_name" content="{{ page.seo.og.site_name }}">
+```
+
+---
+
+## 🎯 Checklist de vérification
+
+Avant de partager sur LinkedIn :
+
+- [ ] L'image fait au moins 1200x630 pixels
+- [ ] L'URL de l'image est absolue (commence par https://)
+- [ ] L'image est accessible publiquement (pas de login requis)
+- [ ] L'image pèse moins de 5 MB
+- [ ] Le format est JPG ou PNG
+- [ ] Toutes les balises og: sont présentes
+- [ ] Vous avez testé avec LinkedIn Post Inspector
+- [ ] Vous avez vidé le cache de LinkedIn si nécessaire
+
+---
+
+## 🔧 Script de diagnostic
+
+Créez ce script pour tester vos métadonnées :
+
+```python
+# test_og_meta.py
+import os
+import django
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'duhaz_blog.settings')
+django.setup()
+
+from blog.models import Blog
+from blog.seo_helpers import SEOMetadata
+from django.test import RequestFactory
+
+# Créer une fausse requête
+factory = RequestFactory()
+request = factory.get('/')
+
+# Récupérer un article
+article = Blog.objects.filter(b_publier=True).first()
+
+if article:
+    # Générer les métadonnées
+    seo_helper = SEOMetadata(request, article)
+    metadata = seo_helper.get_blog_metadata(article)
+    
+    print("=" * 60)
+    print("MÉTADONNÉES OPEN GRAPH")
+    print("=" * 60)
+    print(f"Titre: {metadata['og']['title']}")
+    print(f"Description: {metadata['og']['description']}")
+    print(f"URL: {metadata['og']['url']}")
+    print(f"Image: {metadata['og']['image']}")
+    print(f"Dimensions: {metadata['og']['image_width']}x{metadata['og']['image_height']}")
+    print("=" * 60)
+    
+    # Vérifications
+    print("\nVÉRIFICATIONS:")
+    
+    # URL absolue
+    if metadata['og']['image'].startswith('https://'):
+        print("✅ Image URL est absolue")
+    else:
+        print("❌ Image URL doit être absolue (https://)")
+    
+    # Dimensions
+    if int(metadata['og']['image_width']) >= 1200:
+        print("✅ Largeur suffisante pour LinkedIn")
+    else:
+        print(f"❌ Largeur insuffisante: {metadata['og']['image_width']}px (minimum 1200px)")
+    
+    if int(metadata['og']['image_height']) >= 627:
+        print("✅ Hauteur suffisante pour LinkedIn")
+    else:
+        print(f"❌ Hauteur insuffisante: {metadata['og']['image_height']}px (minimum 627px)")
+else:
+    print("Aucun article publié trouvé")
+```
+
+Exécutez-le :
+```bash
+cd /Users/duhaz/projets/blog-duhaz
+source venv/bin/activate
+python test_og_meta.py
+```
+
+---
+
+## 🚀 Solution rapide
+
+Si vous voulez corriger immédiatement :
+
+1. **Vérifiez vos images** : Assurez-vous qu'elles font au moins 1200x630px
+2. **Testez sur LinkedIn Post Inspector** : https://www.linkedin.com/post-inspector/
+3. **Si ça ne marche toujours pas**, envoyez-moi :
+   - L'URL complète de l'article
+   - Une capture d'écran du code source HTML (les balises og:)
+   - Le résultat de LinkedIn Post Inspector
+
+---
+
+## 📚 Ressources
+
+- [LinkedIn Post Inspector](https://www.linkedin.com/post-inspector/)
+- [Guide Open Graph LinkedIn](https://www.linkedin.com/help/linkedin/answer/a521928)
+- [Open Graph Protocol](https://ogp.me/)
+- [Facebook Sharing Debugger](https://developers.facebook.com/tools/debug/) (pour comparaison)
+
+---
+
+**Prochaine étape** : Testez avec LinkedIn Post Inspector et dites-moi ce qu'il affiche ! 🎯

+ 1 - 0
blog/seo_helpers.py

@@ -99,6 +99,7 @@ class SEOMetadata:
                 'image_alt': image_alt,
                 'image_width': '1200',
                 'image_height': '630',
+                'image_type': 'image/jpeg',
                 'site_name': self.site_name,
                 'locale': 'fr_FR',
                 'article_published_time': published_time,

+ 12 - 0
blog/templates/blog/seo_meta.html

@@ -22,6 +22,18 @@ Usage: {% include 'blog/seo_meta.html' with seo=metadata %}
 <meta property="og:description" content="{{ seo.og.description }}">
 <meta property="og:url" content="{{ seo.og.url }}">
 <meta property="og:image" content="{{ seo.og.image }}">
+{% if seo.og.image_width %}
+<meta property="og:image:width" content="{{ seo.og.image_width }}">
+{% endif %}
+{% if seo.og.image_height %}
+<meta property="og:image:height" content="{{ seo.og.image_height }}">
+{% endif %}
+{% if seo.og.image_type %}
+<meta property="og:image:type" content="{{ seo.og.image_type }}">
+{% endif %}
+{% if seo.og.image_alt %}
+<meta property="og:image:alt" content="{{ seo.og.image_alt }}">
+{% endif %}
 <meta property="og:site_name" content="{{ seo.og.site_name }}">
 <meta property="og:locale" content="{{ seo.og.locale|default:'fr_FR' }}">
 

+ 1 - 0
blog/templates/read.html

@@ -25,6 +25,7 @@
 	<meta property="og:image:alt" content="{{ page.seo.image_alt }}">
 	<meta property="og:image:width" content="{{ page.seo.og.image_width }}">
 	<meta property="og:image:height" content="{{ page.seo.og.image_height }}">
+	<meta property="og:image:type" content="{{ page.seo.og.image_type }}">
 	<meta property="og:site_name" content="{{ page.seo.og.site_name }}">
 	<meta property="og:locale" content="{{ page.seo.og.locale }}">
 	

+ 186 - 0
test_og_meta.py

@@ -0,0 +1,186 @@
+#!/usr/bin/env python3
+"""
+Script de diagnostic des métadonnées Open Graph
+Vérifie que les métadonnées sont correctes pour le partage LinkedIn
+"""
+
+import os
+import sys
+import django
+
+# Configuration Django
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'duhaz_blog.settings')
+django.setup()
+
+from blog.models import Blog
+from blog.seo_helpers import SEOMetadata
+from django.test import RequestFactory
+
+def test_og_metadata():
+    """Teste les métadonnées Open Graph d'un article"""
+    
+    # Créer une fausse requête
+    factory = RequestFactory()
+    request = factory.get('/')
+    
+    # Récupérer le premier article publié
+    article = Blog.objects.filter(b_publier=True).first()
+    
+    if not article:
+        print("❌ Aucun article publié trouvé dans la base de données")
+        return
+    
+    print("=" * 70)
+    print("📊 DIAGNOSTIC DES MÉTADONNÉES OPEN GRAPH")
+    print("=" * 70)
+    print(f"\n📝 Article testé : {article.b_titre}")
+    print(f"🔗 Slug : {article.b_titre_slugify}")
+    print()
+    
+    # Générer les métadonnées
+    seo_helper = SEOMetadata(request, article)
+    metadata = seo_helper.get_blog_metadata(article)
+    
+    # Afficher les métadonnées principales
+    print("=" * 70)
+    print("🔍 MÉTADONNÉES GÉNÉRÉES")
+    print("=" * 70)
+    print(f"Titre         : {metadata['og']['title']}")
+    print(f"Description   : {metadata['og']['description'][:80]}...")
+    print(f"URL           : {metadata['og']['url']}")
+    print(f"Image URL     : {metadata['og']['image']}")
+    print(f"Image Alt     : {metadata['og'].get('image_alt', 'N/A')}")
+    print(f"Image Width   : {metadata['og']['image_width']}px")
+    print(f"Image Height  : {metadata['og']['image_height']}px")
+    print(f"Image Type    : {metadata['og'].get('image_type', 'N/A')}")
+    print(f"Site Name     : {metadata['og']['site_name']}")
+    print(f"Locale        : {metadata['og']['locale']}")
+    print()
+    
+    # Vérifications LinkedIn
+    print("=" * 70)
+    print("✅ VÉRIFICATIONS LINKEDIN")
+    print("=" * 70)
+    
+    checks_passed = 0
+    checks_total = 0
+    
+    # Check 1: URL absolue
+    checks_total += 1
+    if metadata['og']['image'].startswith('https://'):
+        print("✅ Image URL est absolue (commence par https://)")
+        checks_passed += 1
+    else:
+        print(f"❌ Image URL doit être absolue. Actuelle : {metadata['og']['image']}")
+    
+    # Check 2: Largeur minimale
+    checks_total += 1
+    width = int(metadata['og']['image_width'])
+    if width >= 1200:
+        print(f"✅ Largeur suffisante : {width}px (minimum 1200px)")
+        checks_passed += 1
+    else:
+        print(f"❌ Largeur insuffisante : {width}px (minimum requis : 1200px)")
+    
+    # Check 3: Hauteur minimale
+    checks_total += 1
+    height = int(metadata['og']['image_height'])
+    if height >= 627:
+        print(f"✅ Hauteur suffisante : {height}px (minimum 627px)")
+        checks_passed += 1
+    else:
+        print(f"❌ Hauteur insuffisante : {height}px (minimum requis : 627px)")
+    
+    # Check 4: Type d'image spécifié
+    checks_total += 1
+    if 'image_type' in metadata['og'] and metadata['og']['image_type']:
+        print(f"✅ Type d'image spécifié : {metadata['og']['image_type']}")
+        checks_passed += 1
+    else:
+        print("⚠️  Type d'image non spécifié (recommandé pour LinkedIn)")
+    
+    # Check 5: Alt text présent
+    checks_total += 1
+    if 'image_alt' in metadata['og'] and metadata['og']['image_alt']:
+        print(f"✅ Texte alternatif présent")
+        checks_passed += 1
+    else:
+        print("⚠️  Texte alternatif manquant (recommandé pour l'accessibilité)")
+    
+    # Check 6: Toutes les balises essentielles
+    checks_total += 1
+    required_fields = ['type', 'title', 'description', 'url', 'image']
+    missing_fields = [f for f in required_fields if f not in metadata['og'] or not metadata['og'][f]]
+    
+    if not missing_fields:
+        print("✅ Toutes les balises essentielles sont présentes")
+        checks_passed += 1
+    else:
+        print(f"❌ Balises manquantes : {', '.join(missing_fields)}")
+    
+    # Résumé
+    print()
+    print("=" * 70)
+    print("📈 RÉSUMÉ")
+    print("=" * 70)
+    print(f"Tests réussis : {checks_passed}/{checks_total}")
+    
+    if checks_passed == checks_total:
+        print("\n🎉 Excellent ! Vos métadonnées sont optimales pour LinkedIn")
+    elif checks_passed >= checks_total - 1:
+        print("\n👍 Bon ! Quelques améliorations mineures possibles")
+    else:
+        print("\n⚠️  Attention ! Des corrections sont nécessaires pour LinkedIn")
+    
+    # Instructions de test
+    print()
+    print("=" * 70)
+    print("🔧 PROCHAINES ÉTAPES")
+    print("=" * 70)
+    print("\n1. Démarrez le serveur :")
+    print("   ./start.sh")
+    print()
+    print("2. Testez avec LinkedIn Post Inspector :")
+    print("   https://www.linkedin.com/post-inspector/")
+    print()
+    print("3. URL à tester :")
+    print(f"   https://www.duhaz.fr/blog/{article.b_titre_slugify}")
+    print()
+    print("4. Si LinkedIn met en cache l'ancienne version :")
+    print("   - Utilisez le Post Inspector pour forcer le rafraîchissement")
+    print("   - Attendez quelques minutes et réessayez")
+    print()
+    
+    # Informations supplémentaires sur l'image
+    if article.b_description_img:
+        print("=" * 70)
+        print("🖼️  INFORMATIONS SUR L'IMAGE")
+        print("=" * 70)
+        print(f"URL de l'image dans la BDD : {article.b_description_img}")
+        print()
+        if not article.b_description_img.startswith(('http://', 'https://')):
+            print("⚠️  L'URL de l'image est relative.")
+            print("   Elle sera convertie en URL absolue par le helper SEO.")
+        print()
+        print("💡 Vérifiez que cette image :")
+        print("   - Fait au moins 1200x630 pixels")
+        print("   - Est accessible publiquement (pas de login requis)")
+        print("   - Pèse moins de 5 MB")
+        print("   - Est au format JPG ou PNG")
+    else:
+        print("=" * 70)
+        print("⚠️  ATTENTION")
+        print("=" * 70)
+        print("Aucune image spécifiée pour cet article.")
+        print("L'image par défaut sera utilisée.")
+    
+    print("\n" + "=" * 70)
+
+if __name__ == "__main__":
+    try:
+        test_og_metadata()
+    except Exception as e:
+        print(f"\n❌ Erreur lors du test : {e}")
+        import traceback
+        traceback.print_exc()
+        sys.exit(1)