Добавить Open Graph meta-теги для превью лендинга в мессенджерах.

- Добавлены og:title, og:description, og:image, og:url и Twitter Card в landing.html.
- Реализованы normalize_logo_url и build_landing_meta для абсолютных HTTPS-ссылок на логотип.
- landing_page передаёт meta-данные и единый logo_url в шаблон.
- Добавлены тесты нормализации логотипа и проверки OG-тегов.
main
pilot 4 weeks ago
parent a88aeba01a
commit eded036ee2

@ -1,8 +1,13 @@
import json import json
from typing import Callable, Optional, Tuple from typing import Callable, Optional, Tuple
from django.conf import settings
from max_bot.models import Client, Integration from max_bot.models import Client, Integration
CDN_BASE_URL = 'https://cdn.telefon-ip.ru'
DEFAULT_OG_DESCRIPTION = 'Выберите удобный мессанджер.'
def normalize_telegram_url(url: Optional[str]) -> Optional[str]: def normalize_telegram_url(url: Optional[str]) -> Optional[str]:
"""Приводит ссылку на Telegram-бота к виду https://t.me/bot_name.""" """Приводит ссылку на Telegram-бота к виду https://t.me/bot_name."""
@ -104,6 +109,54 @@ def cleanup_max_bot_integration(client: Client) -> None:
_save_settings(integration, settings) _save_settings(integration, settings)
def normalize_logo_url(logo: Optional[str]) -> Optional[str]:
"""Приводит путь логотипа к абсолютному HTTPS URL для og:image и img src."""
if not logo or not str(logo).strip():
return None
value = str(logo).strip()
if value.startswith('https://'):
return value
if value.startswith('http://'):
return f'https://{value[7:]}'
return f'{CDN_BASE_URL}/{value.lstrip("/")}'
def build_landing_meta(
brand: str,
description: str,
logo: str,
client_id: int,
page_url: str,
) -> dict:
"""Формирует meta/OG-данные для превью ссылки в мессенджерах."""
title = brand or 'Ресторан'
meta_description = (description or DEFAULT_OG_DESCRIPTION).strip()
if len(meta_description) > 200:
meta_description = f'{meta_description[:197]}...'
logo_url = normalize_logo_url(logo)
return {
'page_title': title,
'meta_description': meta_description,
'logo_url': logo_url,
'og_title': title,
'og_description': meta_description,
'og_image': logo_url,
'og_url': page_url,
'og_site_name': title,
'og_type': 'website',
'twitter_card': 'summary_large_image' if logo_url else 'summary',
'twitter_title': title,
'twitter_description': meta_description,
'twitter_image': logo_url,
'landing_base_url': settings.BASE_URL,
'client_id': client_id,
}
def get_landing_bot_urls(client: Client) -> Tuple[Optional[str], Optional[str]]: def get_landing_bot_urls(client: Client) -> Tuple[Optional[str], Optional[str]]:
"""Возвращает (max_bot_url, telegram_url) для лендинга.""" """Возвращает (max_bot_url, telegram_url) для лендинга."""
cleanup_max_bot_integration(client) cleanup_max_bot_integration(client)

@ -4,6 +4,8 @@ from unittest.mock import patch, MagicMock
from content_bot.landing_utils import ( from content_bot.landing_utils import (
normalize_telegram_url, normalize_telegram_url,
normalize_max_bot_url, normalize_max_bot_url,
normalize_logo_url,
build_landing_meta,
get_landing_bot_urls, get_landing_bot_urls,
) )
@ -39,6 +41,33 @@ class LandingUrlNormalizationTests(TestCase):
'https://max.ru/d0323350531_bot', 'https://max.ru/d0323350531_bot',
) )
def test_normalize_logo_cdn_path(self):
self.assertEqual(
normalize_logo_url('/cdn/image/kn7m7u.png'),
'https://cdn.telefon-ip.ru/cdn/image/kn7m7u.png',
)
def test_normalize_logo_full_url(self):
self.assertEqual(
normalize_logo_url('https://saper639.ru/storage/app/media/temp/letobar_logo.png'),
'https://saper639.ru/storage/app/media/temp/letobar_logo.png',
)
def test_build_landing_meta_includes_og_tags(self):
meta = build_landing_meta(
brand='Бар Лето',
description='Описание заведения',
logo='/cdn/image/kn7m7u.png',
client_id=2262,
page_url='https://maxbot.telefon-ip.ru/content/landing/2262/',
)
self.assertEqual(meta['og_title'], 'Бар Лето')
self.assertEqual(meta['og_description'], 'Описание заведения')
self.assertEqual(meta['og_image'], 'https://cdn.telefon-ip.ru/cdn/image/kn7m7u.png')
self.assertEqual(meta['og_url'], 'https://maxbot.telefon-ip.ru/content/landing/2262/')
self.assertEqual(meta['twitter_card'], 'summary_large_image')
class LandingBotUrlsTests(TestCase): class LandingBotUrlsTests(TestCase):
@patch('content_bot.landing_utils.Integration') @patch('content_bot.landing_utils.Integration')
@ -58,11 +87,13 @@ class LandingBotUrlsTests(TestCase):
qs.first.return_value = telegram_integration qs.first.return_value = telegram_integration
else: else:
qs.first.return_value = None qs.first.return_value = None
qs.select_related.return_value = qs
return qs return qs
mock_integration.objects.filter.side_effect = filter_side_effect mock_integration.objects.filter.side_effect = filter_side_effect
with patch('content_bot.landing_utils._save_settings') as mock_save: with patch('content_bot.landing_utils.cleanup_max_bot_integration'), \
patch('content_bot.landing_utils._save_settings') as mock_save:
max_url, telegram_url = get_landing_bot_urls(client) max_url, telegram_url = get_landing_bot_urls(client)
self.assertEqual(max_url, 'https://max.ru/d0323350531_bot') self.assertEqual(max_url, 'https://max.ru/d0323350531_bot')
@ -97,3 +128,7 @@ class LandingPageViewTests(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertContains(response, 'https://max.ru/d0323350531_bot') self.assertContains(response, 'https://max.ru/d0323350531_bot')
self.assertContains(response, 'https://t.me/cheguevaraclub_bot') self.assertContains(response, 'https://t.me/cheguevaraclub_bot')
self.assertContains(response, 'property="og:title"')
self.assertContains(response, 'property="og:description"')
self.assertContains(response, 'property="og:image"')
self.assertContains(response, 'property="og:url"')

@ -3,7 +3,7 @@ from django.core.handlers.wsgi import WSGIRequest
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import render, get_object_or_404 from django.shortcuts import render, get_object_or_404
from max_bot.models import Client, Qr from max_bot.models import Client, Qr
from content_bot.landing_utils import get_landing_bot_urls from content_bot.landing_utils import build_landing_meta, get_landing_bot_urls, normalize_logo_url
# Create your views here. # Create your views here.
@ -28,14 +28,25 @@ def landing_page(request, client_id):
client = get_object_or_404(Client, id=client_id, status=True) client = get_object_or_404(Client, id=client_id, status=True)
qr = Qr.objects.filter(client=client).first() qr = Qr.objects.filter(client=client).first()
max_bot_url, telegram_url = get_landing_bot_urls(client) max_bot_url, telegram_url = get_landing_bot_urls(client)
brand = qr.brand if qr and qr.brand else client.brand or 'Ресторан'
logo = qr.logo if qr and qr.logo else client.logo or ''
description = qr.description if qr and qr.description else ''
context = { context = {
'client_id': client.id, 'client_id': client.id,
'brand': qr.brand if qr and qr.brand else client.brand or 'Ресторан', 'brand': brand,
'logo': qr.logo if qr and qr.logo else client.logo or '', 'logo': logo,
'description': qr.description if qr and qr.description else '', 'logo_url': normalize_logo_url(logo),
'description': description,
'max_bot_url': max_bot_url, 'max_bot_url': max_bot_url,
'telegram_url': telegram_url, 'telegram_url': telegram_url,
} }
context.update(build_landing_meta(
brand=brand,
description=description,
logo=logo,
client_id=client.id,
page_url=request.build_absolute_uri(),
))
return render(request, 'max_bot/landing.html', context) return render(request, 'max_bot/landing.html', context)

@ -3,7 +3,24 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>{{ brand|default:"Ресторан" }}</title> <title>{{ page_title|default:brand|default:"Ресторан" }}</title>
<meta name="description" content="{{ meta_description }}">
<meta property="og:type" content="{{ og_type }}">
<meta property="og:title" content="{{ og_title }}">
<meta property="og:description" content="{{ og_description }}">
<meta property="og:url" content="{{ og_url }}">
<meta property="og:site_name" content="{{ og_site_name }}">
{% if og_image %}
<meta property="og:image" content="{{ og_image }}">
<meta property="og:image:secure_url" content="{{ og_image }}">
<meta property="og:image:alt" content="{{ og_title }}">
{% endif %}
<meta name="twitter:card" content="{{ twitter_card }}">
<meta name="twitter:title" content="{{ twitter_title }}">
<meta name="twitter:description" content="{{ twitter_description }}">
{% if twitter_image %}
<meta name="twitter:image" content="{{ twitter_image }}">
{% endif %}
<!-- Font Awesome CDN --> <!-- Font Awesome CDN -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<style> <style>
@ -243,12 +260,8 @@
<div class="container"> <div class="container">
<!-- Логотип --> <!-- Логотип -->
<div class="logo-wrapper"> <div class="logo-wrapper">
{% if logo %} {% if logo_url %}
{% if "http" in logo %} <img src="{{ logo_url }}" alt="{{ brand|default:"Логотип" }}">
<img src="{{ logo }}" alt="{{ brand|default:"Логотип" }}">
{% else %}
<img src="https://cdn.telefon-ip.ru/{{ logo }}" alt="{{ brand|default:"Логотип" }}">
{% endif %}
{% else %} {% else %}
<span class="placeholder">🍽️</span> <span class="placeholder">🍽️</span>
{% endif %} {% endif %}

Loading…
Cancel
Save