Skip to content

Commit 6b183f2

Browse files
committed
Make sure title and descriptions are truncated if too long
1 parent a01c9a8 commit 6b183f2

File tree

9 files changed

+161
-9
lines changed

9 files changed

+161
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
### Changed
88
- Improve developement instructions (@marteinn)
9+
- Make sure title and descriptions are truncated if too long (@marteinn)
910

1011
### Fixed
1112
- Fix broken image preview #12 (@marteinn)

tests/app/models.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
from django.utils.functional import cached_property
2+
13
from django.db import models
24
from django.utils.translation import gettext_lazy as _
35
from wagtail.images import get_image_model_string
46
from wagtail.models import Page
57

68
from .mixins import FacebookModelMixin, MetaModelMixin, TwitterModelMixin
9+
from wagtail_meta_preview.utils import FacebookSettings
710

811

912
class TwitterPage(TwitterModelMixin, Page):
@@ -25,6 +28,18 @@ class FacebookPage(FacebookModelMixin, Page):
2528
another_title = models.CharField(blank=True, max_length=100)
2629
another_description = models.CharField(blank=True, max_length=100)
2730

31+
@cached_property
32+
def facebook_setting(self) -> FacebookSettings:
33+
return FacebookSettings(self)
34+
35+
@cached_property
36+
def seo_og_title(self):
37+
return self.facebook_setting.get_title()
38+
39+
@cached_property
40+
def seo_og_description(self):
41+
return self.facebook_setting.get_description()
42+
2843

2944
class MetaPage(MetaModelMixin, Page):
3045
pass
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>{{ page.title }}</title>
6+
7+
<meta property="og:title" content="{{ page.seo_og_title }}" />
8+
<meta property="og:description" content="{{ page.seo_og_description }}" />
9+
</head>
10+
<body>
11+
<h1>{{ page.title }}</h1>
12+
<pre>
13+
og:title: {{ page.seo_og_title }}
14+
og:description: {{ page.seo_og_description }}
15+
</pre>
16+
</body>
17+
</html>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>{{ page.seo_title }}</title>
6+
7+
<meta name="description" content="{{ page.search_description }}" />
8+
</head>
9+
<body>
10+
<h1>{{ page.title }}</h1>
11+
<pre>
12+
title: {{ page.seo_title }}
13+
meta:description: {{ page.search_description }}
14+
</pre>
15+
</body>
16+
</html>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>{{ page.title }}</title>
6+
7+
<meta property="twitter:title" content="{{ page.twitter_title }}" />
8+
<meta property="twitter:description" content="{{ page.twitter_description }}" />
9+
</head>
10+
<body>
11+
<h1>{{ page.title }}</h1>
12+
<pre>
13+
twitter:title: {{ page.twitter_title }}
14+
twitter:description: {{ page.twitter_description }}
15+
</pre>
16+
</body>
17+
</html>

tests/test_panels_facebook.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,15 @@ def test_facebook_default_without_instance(self):
129129
"default_image": "",
130130
},
131131
)
132+
133+
def test_title_and_description_trunctation(self):
134+
facebook_settings = FacebookSettings(self.facebook_page)
135+
136+
meta_settings.META_PREVIEW_FACEBOOK_TITLE_FIELDS = "og_title"
137+
138+
self.facebook_page.og_title = "This title exeeds the threshold recommended for OG titles and should be capped at max length"
139+
self.facebook_page.og_description = "This description is longer then title and represents the og description field, which should be one to two sentence description of your object, property are optional but recommended."
140+
self.facebook_page.save()
141+
142+
self.assertEqual(len(facebook_settings.get_title()), 90)
143+
self.assertEqual(len(facebook_settings.get_description()), 160)

tests/test_panels_google.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,16 @@ def test_google_default_without_instance(self):
9696
"default_image": "",
9797
},
9898
)
99+
100+
def test_title_and_description_trunctation(self):
101+
google_settings = GoogleSettings(self.google_page)
102+
103+
meta_settings.META_PREVIEW_GOOGLE_TITLE_FIELDS = "seo_title"
104+
meta_settings.META_PREVIEW_GOOGLE_DESCRIPTION_FIELDS = "search_description"
105+
106+
self.google_page.seo_title = "This title exeeds the threshold recommended for twitter titles and should be capped at max length"
107+
self.google_page.search_description = "This text is exactly 200 characters long, designed to convey the idea of counting characters. The goal is to show how we can be precise with word choices, making sure to reach the exact character count! Everything outside this is capped."
108+
self.google_page.save()
109+
110+
self.assertEqual(len(google_settings.get_title()), 70)
111+
self.assertEqual(len(google_settings.get_description()), 160)

tests/test_panels_twitter.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,16 @@ def test_twitter_default_without_instance(self):
128128
"default_image": "",
129129
},
130130
)
131+
132+
def test_title_and_description_trunctation(self):
133+
twitter_settings = TwitterSettings(self.twitter_page)
134+
135+
meta_settings.META_PREVIEW_TWITTER_TITLE_FIELDS = "title"
136+
meta_settings.META_PREVIEW_TWITTER_DESCRIPTION_FIELDS = "search_description"
137+
138+
self.twitter_page.title = "This title exeeds the threshold recommended for twitter titles and should be capped at max length"
139+
self.twitter_page.search_description = "This text is exactly 200 characters long, designed to convey the idea of counting characters. The goal is to show how we can be precise with word choices, making sure to reach the exact character count! Everything outside this is capped."
140+
self.twitter_page.save()
141+
142+
self.assertEqual(len(twitter_settings.get_title()), 70)
143+
self.assertEqual(len(twitter_settings.get_description()), 200)

wagtail_meta_preview/utils.py

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
from typing import Union, Literal
2+
3+
from django.utils.text import Truncator
4+
15
from . import meta_settings
26

37

@@ -31,10 +35,20 @@ class BaseSettings:
3135
"FACEBOOK": "META_PREVIEW_FACEBOOK_IMAGE_FIELDS",
3236
}
3337

38+
TYPE_GOOGLE = "GOOGLE"
39+
TYPE_TWITTER = "TWITTER"
40+
TYPE_FACEBOOK = "FACEBOOK"
41+
42+
TYPE = Literal["GOOGLE", "TWITTER", "FACEBOOK"]
43+
44+
type: TYPE
45+
title_max_chars: int = -1
46+
description_max_chars: int = -1
47+
3448
def __init__(self, instance=None):
3549
self.instance = instance
3650

37-
def get_title(self):
51+
def get_title(self) -> str:
3852
if not self.instance:
3953
return ""
4054

@@ -50,7 +64,14 @@ def get_title(self):
5064
except StopIteration:
5165
return ""
5266

53-
return getattr(self.instance, title_field) or ""
67+
value = getattr(self.instance, title_field) or ""
68+
if self.title_max_chars == -1:
69+
return value
70+
71+
return self._get_truncated_str(value, self.title_max_chars)
72+
73+
def _get_truncated_str(self, value: str, max_chars) -> str:
74+
return Truncator(value).chars(max_chars)
5475

5576
def get_description(self):
5677
if not self.instance:
@@ -68,7 +89,11 @@ def get_description(self):
6889
except StopIteration:
6990
return ""
7091

71-
return getattr(self.instance, description_field) or ""
92+
value = getattr(self.instance, description_field) or ""
93+
if self.description_max_chars == -1:
94+
return value
95+
96+
return self._get_truncated_str(value, self.description_max_chars)
7297

7398
def get_image(self):
7499
if not self.instance or self.type == "GOOGLE":
@@ -117,18 +142,41 @@ def get_defaults(self):
117142

118143

119144
class TwitterSettings(BaseSettings):
120-
def __init__(self, instance=None):
121-
self.type = "TWITTER"
145+
def __init__(
146+
self,
147+
instance=None,
148+
title_max_chars: int = 70,
149+
description_max_chars: int = 200,
150+
):
151+
self.type = self.TYPE_TWITTER
152+
self.title_max_chars = title_max_chars
153+
self.description_max_chars = description_max_chars
154+
122155
super().__init__(instance)
123156

124157

125158
class FacebookSettings(BaseSettings):
126-
def __init__(self, instance=None):
127-
self.type = "FACEBOOK"
159+
def __init__(
160+
self,
161+
instance=None,
162+
title_max_chars: int = 90,
163+
description_max_chars: int = 160,
164+
):
165+
self.type = self.TYPE_FACEBOOK
166+
self.title_max_chars = title_max_chars
167+
self.description_max_chars = description_max_chars
168+
128169
super().__init__(instance)
129170

130171

131172
class GoogleSettings(BaseSettings):
132-
def __init__(self, instance=None):
133-
self.type = "GOOGLE"
173+
def __init__(
174+
self,
175+
instance=None,
176+
title_max_chars: int = 70,
177+
description_max_chars: int = 160,
178+
):
179+
self.type = self.TYPE_GOOGLE
180+
self.title_max_chars = title_max_chars
181+
self.description_max_chars = description_max_chars
134182
super().__init__(instance)

0 commit comments

Comments
 (0)