Add image dimensions to models, and add backfill command
This commit is contained in:
parent
85bdf572c5
commit
4727657285
7 changed files with 299 additions and 7 deletions
|
|
@ -8,6 +8,7 @@ dependencies = [
|
||||||
"dateparser",
|
"dateparser",
|
||||||
"django",
|
"django",
|
||||||
"json-repair",
|
"json-repair",
|
||||||
|
"pillow",
|
||||||
"platformdirs",
|
"platformdirs",
|
||||||
"python-dotenv",
|
"python-dotenv",
|
||||||
"pygments",
|
"pygments",
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
{# - page_title: str - Page title (defaults to "ttvdrops") #}
|
{# - page_title: str - Page title (defaults to "ttvdrops") #}
|
||||||
{# - page_description: str - Page description (defaults to site description) #}
|
{# - page_description: str - Page description (defaults to site description) #}
|
||||||
{# - page_image: str - Image URL for og:image (optional) #}
|
{# - page_image: str - Image URL for og:image (optional) #}
|
||||||
|
{# - page_image_width: int - Image width in pixels (optional) #}
|
||||||
|
{# - page_image_height: int - Image height in pixels (optional) #}
|
||||||
{# - page_url: str - Full URL for og:url and canonical (defaults to request.build_absolute_uri) #}
|
{# - page_url: str - Full URL for og:url and canonical (defaults to request.build_absolute_uri) #}
|
||||||
{# - og_type: str - OpenGraph type (defaults to "website") #}
|
{# - og_type: str - OpenGraph type (defaults to "website") #}
|
||||||
{# - schema_data: str - JSON-LD schema data serialized as string (optional) #}
|
{# - schema_data: str - JSON-LD schema data serialized as string (optional) #}
|
||||||
|
|
@ -36,8 +38,10 @@
|
||||||
content="{% firstof page_url request.build_absolute_uri %}" />
|
content="{% firstof page_url request.build_absolute_uri %}" />
|
||||||
{% if page_image %}
|
{% if page_image %}
|
||||||
<meta property="og:image" content="{{ page_image }}" />
|
<meta property="og:image" content="{{ page_image }}" />
|
||||||
<meta property="og:image:width" content="1200" />
|
{% if page_image_width and page_image_height %}
|
||||||
<meta property="og:image:height" content="630" />
|
<meta property="og:image:width" content="{{ page_image_width }}" />
|
||||||
|
<meta property="og:image:height" content="{{ page_image_height }}" />
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{# Twitter Card tags for rich previews #}
|
{# Twitter Card tags for rich previews #}
|
||||||
<meta name="twitter:card"
|
<meta name="twitter:card"
|
||||||
|
|
|
||||||
73
twitch/management/commands/backfill_image_dimensions.py
Normal file
73
twitch/management/commands/backfill_image_dimensions.py
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
"""Management command to backfill image dimensions for existing cached images."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from twitch.models import DropBenefit
|
||||||
|
from twitch.models import DropCampaign
|
||||||
|
from twitch.models import Game
|
||||||
|
from twitch.models import RewardCampaign
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
"""Backfill image width and height fields for existing cached images."""
|
||||||
|
|
||||||
|
help = "Backfill image dimensions for existing cached images"
|
||||||
|
|
||||||
|
def handle(self, *args, **options) -> None: # noqa: ARG002
|
||||||
|
"""Execute the command."""
|
||||||
|
total_updated = 0
|
||||||
|
|
||||||
|
# Update Game box art
|
||||||
|
self.stdout.write("Processing Game box_art_file...")
|
||||||
|
for game in Game.objects.exclude(box_art_file=""):
|
||||||
|
if game.box_art_file and not game.box_art_width:
|
||||||
|
try:
|
||||||
|
# Opening the file and saving triggers dimension calculation
|
||||||
|
game.box_art_file.open()
|
||||||
|
game.save()
|
||||||
|
total_updated += 1
|
||||||
|
self.stdout.write(self.style.SUCCESS(f" Updated {game}"))
|
||||||
|
except (OSError, ValueError, AttributeError) as exc:
|
||||||
|
self.stdout.write(self.style.ERROR(f" Failed {game}: {exc}"))
|
||||||
|
|
||||||
|
# Update DropCampaign images
|
||||||
|
self.stdout.write("Processing DropCampaign image_file...")
|
||||||
|
for campaign in DropCampaign.objects.exclude(image_file=""):
|
||||||
|
if campaign.image_file and not campaign.image_width:
|
||||||
|
try:
|
||||||
|
campaign.image_file.open()
|
||||||
|
campaign.save()
|
||||||
|
total_updated += 1
|
||||||
|
self.stdout.write(self.style.SUCCESS(f" Updated {campaign}"))
|
||||||
|
except (OSError, ValueError, AttributeError) as exc:
|
||||||
|
self.stdout.write(self.style.ERROR(f" Failed {campaign}: {exc}"))
|
||||||
|
|
||||||
|
# Update DropBenefit images
|
||||||
|
self.stdout.write("Processing DropBenefit image_file...")
|
||||||
|
for benefit in DropBenefit.objects.exclude(image_file=""):
|
||||||
|
if benefit.image_file and not benefit.image_width:
|
||||||
|
try:
|
||||||
|
benefit.image_file.open()
|
||||||
|
benefit.save()
|
||||||
|
total_updated += 1
|
||||||
|
self.stdout.write(self.style.SUCCESS(f" Updated {benefit}"))
|
||||||
|
except (OSError, ValueError, AttributeError) as exc:
|
||||||
|
self.stdout.write(self.style.ERROR(f" Failed {benefit}: {exc}"))
|
||||||
|
|
||||||
|
# Update RewardCampaign images
|
||||||
|
self.stdout.write("Processing RewardCampaign image_file...")
|
||||||
|
for reward in RewardCampaign.objects.exclude(image_file=""):
|
||||||
|
if reward.image_file and not reward.image_width:
|
||||||
|
try:
|
||||||
|
reward.image_file.open()
|
||||||
|
reward.save()
|
||||||
|
total_updated += 1
|
||||||
|
self.stdout.write(self.style.SUCCESS(f" Updated {reward}"))
|
||||||
|
except (OSError, ValueError, AttributeError) as exc:
|
||||||
|
self.stdout.write(self.style.ERROR(f" Failed {reward}: {exc}"))
|
||||||
|
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.SUCCESS(f"\nBackfill complete! Updated {total_updated} images."),
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
# Generated by Django 6.0.2 on 2026-02-12 03:41
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
"""Add image height and width fields to DropBenefit, DropCampaign, Game, and RewardCampaign, then update ImageFields to use them.""" # noqa: E501
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("twitch", "0010_rewardcampaign_image_file_rewardcampaign_image_url"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="dropbenefit",
|
||||||
|
name="image_height",
|
||||||
|
field=models.PositiveIntegerField(
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
help_text="Height of cached image in pixels.",
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="dropbenefit",
|
||||||
|
name="image_width",
|
||||||
|
field=models.PositiveIntegerField(
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
help_text="Width of cached image in pixels.",
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="dropcampaign",
|
||||||
|
name="image_height",
|
||||||
|
field=models.PositiveIntegerField(
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
help_text="Height of cached image in pixels.",
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="dropcampaign",
|
||||||
|
name="image_width",
|
||||||
|
field=models.PositiveIntegerField(
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
help_text="Width of cached image in pixels.",
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="game",
|
||||||
|
name="box_art_height",
|
||||||
|
field=models.PositiveIntegerField(
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
help_text="Height of cached box art image in pixels.",
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="game",
|
||||||
|
name="box_art_width",
|
||||||
|
field=models.PositiveIntegerField(
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
help_text="Width of cached box art image in pixels.",
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="rewardcampaign",
|
||||||
|
name="image_height",
|
||||||
|
field=models.PositiveIntegerField(
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
help_text="Height of cached image in pixels.",
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="rewardcampaign",
|
||||||
|
name="image_width",
|
||||||
|
field=models.PositiveIntegerField(
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
help_text="Width of cached image in pixels.",
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="dropbenefit",
|
||||||
|
name="image_file",
|
||||||
|
field=models.ImageField(
|
||||||
|
blank=True,
|
||||||
|
height_field="image_height",
|
||||||
|
help_text="Locally cached benefit image served from this site.",
|
||||||
|
null=True,
|
||||||
|
upload_to="benefits/images/",
|
||||||
|
width_field="image_width",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="dropcampaign",
|
||||||
|
name="image_file",
|
||||||
|
field=models.ImageField(
|
||||||
|
blank=True,
|
||||||
|
height_field="image_height",
|
||||||
|
help_text="Locally cached campaign image served from this site.",
|
||||||
|
null=True,
|
||||||
|
upload_to="campaigns/images/",
|
||||||
|
width_field="image_width",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="game",
|
||||||
|
name="box_art_file",
|
||||||
|
field=models.ImageField(
|
||||||
|
blank=True,
|
||||||
|
height_field="box_art_height",
|
||||||
|
help_text="Locally cached box art image served from this site.",
|
||||||
|
null=True,
|
||||||
|
upload_to="games/box_art/",
|
||||||
|
width_field="box_art_width",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="rewardcampaign",
|
||||||
|
name="image_file",
|
||||||
|
field=models.ImageField(
|
||||||
|
blank=True,
|
||||||
|
height_field="image_height",
|
||||||
|
help_text="Locally cached reward campaign image served from this site.",
|
||||||
|
null=True,
|
||||||
|
upload_to="reward_campaigns/images/",
|
||||||
|
width_field="image_width",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -104,12 +104,26 @@ class Game(auto_prefetch.Model):
|
||||||
verbose_name="Box art URL",
|
verbose_name="Box art URL",
|
||||||
)
|
)
|
||||||
|
|
||||||
box_art_file = models.FileField(
|
box_art_file = models.ImageField(
|
||||||
upload_to="games/box_art/",
|
upload_to="games/box_art/",
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
|
width_field="box_art_width",
|
||||||
|
height_field="box_art_height",
|
||||||
help_text="Locally cached box art image served from this site.",
|
help_text="Locally cached box art image served from this site.",
|
||||||
)
|
)
|
||||||
|
box_art_width = models.PositiveIntegerField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
help_text="Width of cached box art image in pixels.",
|
||||||
|
)
|
||||||
|
box_art_height = models.PositiveIntegerField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
help_text="Height of cached box art image in pixels.",
|
||||||
|
)
|
||||||
|
|
||||||
owners = models.ManyToManyField(
|
owners = models.ManyToManyField(
|
||||||
Organization,
|
Organization,
|
||||||
|
|
@ -332,12 +346,26 @@ class DropCampaign(auto_prefetch.Model):
|
||||||
default="",
|
default="",
|
||||||
help_text="URL to an image representing the campaign.",
|
help_text="URL to an image representing the campaign.",
|
||||||
)
|
)
|
||||||
image_file = models.FileField(
|
image_file = models.ImageField(
|
||||||
upload_to="campaigns/images/",
|
upload_to="campaigns/images/",
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
|
width_field="image_width",
|
||||||
|
height_field="image_height",
|
||||||
help_text="Locally cached campaign image served from this site.",
|
help_text="Locally cached campaign image served from this site.",
|
||||||
)
|
)
|
||||||
|
image_width = models.PositiveIntegerField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
help_text="Width of cached image in pixels.",
|
||||||
|
)
|
||||||
|
image_height = models.PositiveIntegerField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
help_text="Height of cached image in pixels.",
|
||||||
|
)
|
||||||
start_at = models.DateTimeField(
|
start_at = models.DateTimeField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
|
|
@ -577,12 +605,26 @@ class DropBenefit(auto_prefetch.Model):
|
||||||
default="",
|
default="",
|
||||||
help_text="URL to the benefit's image asset.",
|
help_text="URL to the benefit's image asset.",
|
||||||
)
|
)
|
||||||
image_file = models.FileField(
|
image_file = models.ImageField(
|
||||||
upload_to="benefits/images/",
|
upload_to="benefits/images/",
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
|
width_field="image_width",
|
||||||
|
height_field="image_height",
|
||||||
help_text="Locally cached benefit image served from this site.",
|
help_text="Locally cached benefit image served from this site.",
|
||||||
)
|
)
|
||||||
|
image_width = models.PositiveIntegerField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
help_text="Width of cached image in pixels.",
|
||||||
|
)
|
||||||
|
image_height = models.PositiveIntegerField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
help_text="Height of cached image in pixels.",
|
||||||
|
)
|
||||||
created_at = models.DateTimeField(
|
created_at = models.DateTimeField(
|
||||||
null=True,
|
null=True,
|
||||||
help_text=("Timestamp when the benefit was created. This is from Twitch API and not auto-generated."),
|
help_text=("Timestamp when the benefit was created. This is from Twitch API and not auto-generated."),
|
||||||
|
|
@ -834,12 +876,26 @@ class RewardCampaign(auto_prefetch.Model):
|
||||||
default="",
|
default="",
|
||||||
help_text="URL to an image representing the reward campaign.",
|
help_text="URL to an image representing the reward campaign.",
|
||||||
)
|
)
|
||||||
image_file = models.FileField(
|
image_file = models.ImageField(
|
||||||
upload_to="reward_campaigns/images/",
|
upload_to="reward_campaigns/images/",
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
|
width_field="image_width",
|
||||||
|
height_field="image_height",
|
||||||
help_text="Locally cached reward campaign image served from this site.",
|
help_text="Locally cached reward campaign image served from this site.",
|
||||||
)
|
)
|
||||||
|
image_width = models.PositiveIntegerField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
help_text="Width of cached image in pixels.",
|
||||||
|
)
|
||||||
|
image_height = models.PositiveIntegerField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
help_text="Height of cached image in pixels.",
|
||||||
|
)
|
||||||
is_sitewide = models.BooleanField(
|
is_sitewide = models.BooleanField(
|
||||||
default=False,
|
default=False,
|
||||||
help_text="Whether the reward campaign is sitewide.",
|
help_text="Whether the reward campaign is sitewide.",
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ from django.utils import timezone
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
TWITCH_BOX_ART_HOST = "static-cdn.jtvnw.net"
|
TWITCH_BOX_ART_HOST = "static-cdn.jtvnw.net"
|
||||||
TWITCH_BOX_ART_PATH_PREFIX = "/ttv-boxart/"
|
TWITCH_BOX_ART_PATH_PREFIX = "/ttv-boxart/"
|
||||||
TWITCH_BOX_ART_SIZE_PATTERN: re.Pattern[str] = re.compile(r"-(\{width\}|\d+)x(\{height\}|\d+)(?=\.[A-Za-z0-9]+$)")
|
TWITCH_BOX_ART_SIZE_PATTERN: re.Pattern[str] = re.compile(r"-(\{width\}|\d+)x(\{height\}|\d+)(?=\.[A-Za-z0-9]+$)")
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,8 @@ def _build_seo_context( # noqa: PLR0913, PLR0917
|
||||||
page_title: str = "ttvdrops",
|
page_title: str = "ttvdrops",
|
||||||
page_description: str | None = None,
|
page_description: str | None = None,
|
||||||
page_image: str | None = None,
|
page_image: str | None = None,
|
||||||
|
page_image_width: int | None = None,
|
||||||
|
page_image_height: int | None = None,
|
||||||
og_type: str = "website",
|
og_type: str = "website",
|
||||||
schema_data: dict[str, Any] | None = None,
|
schema_data: dict[str, Any] | None = None,
|
||||||
breadcrumb_schema: dict[str, Any] | None = None,
|
breadcrumb_schema: dict[str, Any] | None = None,
|
||||||
|
|
@ -109,6 +111,8 @@ def _build_seo_context( # noqa: PLR0913, PLR0917
|
||||||
page_title: Page title (shown in browser tab, og:title).
|
page_title: Page title (shown in browser tab, og:title).
|
||||||
page_description: Page description (meta description, og:description).
|
page_description: Page description (meta description, og:description).
|
||||||
page_image: Image URL for og:image meta tag.
|
page_image: Image URL for og:image meta tag.
|
||||||
|
page_image_width: Width of the image in pixels.
|
||||||
|
page_image_height: Height of the image in pixels.
|
||||||
og_type: OpenGraph type (e.g., "website", "article").
|
og_type: OpenGraph type (e.g., "website", "article").
|
||||||
schema_data: Dict representation of Schema.org JSON-LD data.
|
schema_data: Dict representation of Schema.org JSON-LD data.
|
||||||
breadcrumb_schema: Breadcrumb schema dict for navigation hierarchy.
|
breadcrumb_schema: Breadcrumb schema dict for navigation hierarchy.
|
||||||
|
|
@ -128,6 +132,9 @@ def _build_seo_context( # noqa: PLR0913, PLR0917
|
||||||
}
|
}
|
||||||
if page_image:
|
if page_image:
|
||||||
context["page_image"] = page_image
|
context["page_image"] = page_image
|
||||||
|
if page_image_width and page_image_height:
|
||||||
|
context["page_image_width"] = page_image_width
|
||||||
|
context["page_image_height"] = page_image_height
|
||||||
if schema_data:
|
if schema_data:
|
||||||
context["schema_data"] = json.dumps(schema_data)
|
context["schema_data"] = json.dumps(schema_data)
|
||||||
if breadcrumb_schema:
|
if breadcrumb_schema:
|
||||||
|
|
@ -858,6 +865,8 @@ def drop_campaign_detail_view(request: HttpRequest, twitch_id: str) -> HttpRespo
|
||||||
else f"Twitch drop campaign: {campaign_name}"
|
else f"Twitch drop campaign: {campaign_name}"
|
||||||
)
|
)
|
||||||
campaign_image: str | None = campaign.image_best_url
|
campaign_image: str | None = campaign.image_best_url
|
||||||
|
campaign_image_width: int | None = campaign.image_width if campaign.image_file else None
|
||||||
|
campaign_image_height: int | None = campaign.image_height if campaign.image_file else None
|
||||||
|
|
||||||
campaign_schema: dict[str, str | dict[str, str]] = {
|
campaign_schema: dict[str, str | dict[str, str]] = {
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
|
|
@ -905,6 +914,8 @@ def drop_campaign_detail_view(request: HttpRequest, twitch_id: str) -> HttpRespo
|
||||||
page_title=campaign_name,
|
page_title=campaign_name,
|
||||||
page_description=campaign_description,
|
page_description=campaign_description,
|
||||||
page_image=campaign_image,
|
page_image=campaign_image,
|
||||||
|
page_image_width=campaign_image_width,
|
||||||
|
page_image_height=campaign_image_height,
|
||||||
schema_data=campaign_schema,
|
schema_data=campaign_schema,
|
||||||
breadcrumb_schema=breadcrumb_schema,
|
breadcrumb_schema=breadcrumb_schema,
|
||||||
modified_date=campaign.updated_at.isoformat() if campaign.updated_at else None,
|
modified_date=campaign.updated_at.isoformat() if campaign.updated_at else None,
|
||||||
|
|
@ -1174,6 +1185,8 @@ class GameDetailView(DetailView):
|
||||||
f"Twitch drop campaigns for {game_name}. View active, upcoming, and completed drop rewards."
|
f"Twitch drop campaigns for {game_name}. View active, upcoming, and completed drop rewards."
|
||||||
)
|
)
|
||||||
game_image: str | None = game.box_art_best_url
|
game_image: str | None = game.box_art_best_url
|
||||||
|
game_image_width: int | None = game.box_art_width if game.box_art_file else None
|
||||||
|
game_image_height: int | None = game.box_art_height if game.box_art_file else None
|
||||||
|
|
||||||
game_schema: dict[str, Any] = {
|
game_schema: dict[str, Any] = {
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
|
|
@ -1204,6 +1217,8 @@ class GameDetailView(DetailView):
|
||||||
page_title=game_name,
|
page_title=game_name,
|
||||||
page_description=game_description,
|
page_description=game_description,
|
||||||
page_image=game_image,
|
page_image=game_image,
|
||||||
|
page_image_width=game_image_width,
|
||||||
|
page_image_height=game_image_height,
|
||||||
schema_data=game_schema,
|
schema_data=game_schema,
|
||||||
breadcrumb_schema=breadcrumb_schema,
|
breadcrumb_schema=breadcrumb_schema,
|
||||||
modified_date=game.updated_at.isoformat() if game.updated_at else None,
|
modified_date=game.updated_at.isoformat() if game.updated_at else None,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue