Skip to content

Commit f382469

Browse files
committed
Added blog.Entry.social_media_card field
1 parent 5e873f6 commit f382469

File tree

4 files changed

+71
-2
lines changed

4 files changed

+71
-2
lines changed

blog/admin.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class EntryAdmin(admin.ModelAdmin):
1515
list_filter = ("is_active",)
1616
exclude = ("summary_html", "body_html")
1717
prepopulated_fields = {"slug": ("headline",)}
18+
raw_id_fields = ["social_media_card"]
1819

1920
def formfield_for_dbfield(self, db_field, **kwargs):
2021
formfield = super().formfield_for_dbfield(db_field, **kwargs)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Generated by Django 6.0.dev20250403184043 on 2025-04-11 07:55
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('blog', '0004_imageupload'),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name='entry',
16+
name='social_media_card',
17+
field=models.ForeignKey(blank=True, help_text='For maximum compatibility, the image should be < 5 MB and at least 1200x627 px.', null=True, on_delete=django.db.models.deletion.PROTECT, to='blog.imageupload'),
18+
),
19+
]

blog/models.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from django.utils.cache import _generate_cache_header_key
1010
from django.utils.formats import date_format
1111
from django.utils.translation import gettext_lazy as _
12-
from django_hosts.resolvers import reverse
12+
from django_hosts.resolvers import get_host, reverse, reverse_host
1313
from docutils.core import publish_parts
1414
from markdown import markdown
1515
from markdown.extensions.toc import TocExtension, slugify as _md_title_slugify
@@ -98,6 +98,21 @@ class ImageUpload(models.Model):
9898
class Meta:
9999
ordering = ("-uploaded_on",)
100100

101+
def __str__(self):
102+
return f"({self.uploaded_on.date()}) {self.title}"
103+
104+
@property
105+
def full_url(self):
106+
"""
107+
Return a full URL (scheme + hostname + path) to the image
108+
"""
109+
p = urlparse(self.image.url)
110+
if p.netloc:
111+
return self.image.url
112+
host = get_host()
113+
hostname = reverse_host(host)
114+
return f"{host.scheme}{hostname}{host.port}{self.image.url}"
115+
101116

102117
class Entry(models.Model):
103118
headline = models.CharField(max_length=200)
@@ -123,6 +138,16 @@ class Entry(models.Model):
123138
body = models.TextField()
124139
body_html = models.TextField()
125140
author = models.CharField(max_length=100)
141+
social_media_card = models.ForeignKey(
142+
ImageUpload,
143+
on_delete=models.PROTECT,
144+
blank=True,
145+
null=True,
146+
help_text=_(
147+
"For maximum compatibility, the image should be < 5 MB "
148+
"and at least 1200x627 px."
149+
),
150+
)
126151

127152
objects = EntryQuerySet.as_manager()
128153

@@ -179,7 +204,7 @@ def invalidate_cached_entry(self):
179204

180205
@property
181206
def opengraph_tags(self):
182-
return {
207+
tags = {
183208
"og:type": "article",
184209
"og:title": self.headline,
185210
"og:description": _("Posted by {author} on {pub_date}").format(
@@ -195,6 +220,13 @@ def opengraph_tags(self):
195220
"twitter:creator": "djangoproject",
196221
"twitter:site": "djangoproject",
197222
}
223+
if card := self.social_media_card:
224+
tags |= {
225+
"og:image": card.full_url,
226+
"og:image:alt": card.alt_text,
227+
}
228+
229+
return tags
198230

199231

200232
class EventQuerySet(EntryQuerySet):

blog/tests.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from datetime import date, timedelta
33
from io import StringIO
44

5+
import time_machine
56
from django.contrib.auth.models import User
67
from django.core.files.base import ContentFile
78
from django.test import TestCase
@@ -280,3 +281,19 @@ def test_contentformat_image_tags(self):
280281
cf.img(url="/test/image.png", alt_text="TEST"),
281282
expected,
282283
)
284+
285+
@time_machine.travel("2005-07-21")
286+
def test_full_url(self):
287+
i = ImageUpload.objects.create(
288+
title="test",
289+
alt_text="test",
290+
image=ContentFile(b".", name="test.png"),
291+
)
292+
# Because the storage is persistent between test runs, running this
293+
# test twice will trigger a filename clash and the storage will append
294+
# a random suffix to the filename, hence the use of assertRegex here.
295+
self.assertRegex(
296+
i.full_url,
297+
r"http://www\.djangoproject\.localhost:8000"
298+
r"/m/blog/images/2005/07/test(_\w+)?\.png",
299+
)

0 commit comments

Comments
 (0)