From 6b689704b0e06848544a88ccecc3e8df17d50efb Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 8 Jul 2025 16:02:12 +0200 Subject: [PATCH] add support for article specific og image --- hub/services/admin/articles.py | 4 +-- .../0045_add_og_image_to_article.py | 25 +++++++++++++++++++ hub/services/models/articles.py | 18 +++++++++++++ hub/services/templatetags/social_meta_tags.py | 6 ++--- uv.lock | 2 +- 5 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 hub/services/migrations/0045_add_og_image_to_article.py diff --git a/hub/services/admin/articles.py b/hub/services/admin/articles.py index 916350d..5050c76 100644 --- a/hub/services/admin/articles.py +++ b/hub/services/admin/articles.py @@ -69,8 +69,8 @@ class ArticleAdmin(admin.ModelAdmin): ( "Images", { - "fields": ("image_library",), - "description": "Select an image from the Image Library.", + "fields": ("image_library", "og_image"), + "description": "Select an image from the Image Library and optionally upload a specific Open Graph image for social sharing.", }, ), ( diff --git a/hub/services/migrations/0045_add_og_image_to_article.py b/hub/services/migrations/0045_add_og_image_to_article.py new file mode 100644 index 0000000..ff4d13d --- /dev/null +++ b/hub/services/migrations/0045_add_og_image_to_article.py @@ -0,0 +1,25 @@ +# Generated by Django 5.2 on 2025-07-08 13:53 + +import hub.services.models.base +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("services", "0044_add_svg_support"), + ] + + operations = [ + migrations.AddField( + model_name="article", + name="og_image", + field=models.ImageField( + blank=True, + help_text="Optional Open Graph image for social sharing (max 1MB). If not provided, the article's main image will be used.", + null=True, + upload_to="article_og_images/", + validators=[hub.services.models.base.validate_image_size], + ), + ), + ] diff --git a/hub/services/models/articles.py b/hub/services/models/articles.py index 0ab8251..aafba9b 100644 --- a/hub/services/models/articles.py +++ b/hub/services/models/articles.py @@ -51,6 +51,15 @@ class Article(ImageReference): help_text="Link this article to a cloud provider", ) + # Open Graph image for social sharing + og_image = models.ImageField( + upload_to="article_og_images/", + blank=True, + null=True, + validators=[validate_image_size], + help_text="Optional Open Graph image for social sharing (max 1MB). If not provided, the article's main image will be used." + ) + # Publishing controls is_published = models.BooleanField( default=False, help_text="Only published articles are visible to users" @@ -90,6 +99,15 @@ class Article(ImageReference): return self.image_library.image return None + @property + def get_og_image(self): + """Returns the Open Graph image for social sharing""" + # Use specific OG image if available + if self.og_image: + return self.og_image + # Fall back to main article image + return self.get_image + @property def related_to(self): """Returns a string describing what this article is related to""" diff --git a/hub/services/templatetags/social_meta_tags.py b/hub/services/templatetags/social_meta_tags.py index fdaf0a3..3bf953e 100644 --- a/hub/services/templatetags/social_meta_tags.py +++ b/hub/services/templatetags/social_meta_tags.py @@ -63,9 +63,9 @@ def social_meta_tags(context): article = context["article"] title = f"Servala - {article.title}" description = article.excerpt - # Use article image if available, otherwise default - if article.get_image: - image_url = request.build_absolute_uri(article.get_image.url) + # Use OG image if available, otherwise fall back to article image, then default + if article.get_og_image: + image_url = request.build_absolute_uri(article.get_og_image.url) # Determine og:type based on view og_type = "website" # default diff --git a/uv.lock b/uv.lock index 2a5954a..5c335f2 100644 --- a/uv.lock +++ b/uv.lock @@ -384,7 +384,7 @@ requires-dist = [ { name = "django-import-export", specifier = ">=4.3.7" }, { name = "django-jazzmin", specifier = ">=3.0.1" }, { name = "django-nested-admin", specifier = ">=4.1.1" }, - { name = "django-prose-editor", extras = ["sanitize"], specifier = ">=0.10.3" }, + { name = "django-prose-editor", extras = ["sanitize"], specifier = ">=0.15.0" }, { name = "django-schema-viewer", specifier = ">=0.5.2" }, { name = "djangorestframework", specifier = ">=3.15.2" }, { name = "environs", extras = ["django"], specifier = "~=14.0" },