Rewrite how trade offer png's are generated to try to reduce system resources. Only render the specific html code necessary and not the entire trade offer details page.
This commit is contained in:
parent
15f8eb7cf4
commit
d5f8345581
9 changed files with 239 additions and 85 deletions
|
|
@ -23,7 +23,7 @@ COPY .env.production /code/.env
|
||||||
ENV HOME=/code
|
ENV HOME=/code
|
||||||
|
|
||||||
# Install NPM & node.js
|
# Install NPM & node.js
|
||||||
RUN apt-get update && apt-get install -y nodejs npm xvfb libnss3 libnspr4 libasound2t64 libatk-bridge2.0-0 libatk1.0-0 libatspi2.0-0 libcairo2 libcups2 libdbus-1-3 libdrm2 libgbm1 libnspr4 libnss3 libpango-1.0-0 libx11-6 libxcb1 libxcomposite1 libxdamage1 libxext6 libxfixes3 libxkbcommon0 libxrandr2 libcairo-gobject2 libdbus-glib-1-2 libfontconfig1 libfreetype6 libgdk-pixbuf-2.0-0 libgtk-3-0 libharfbuzz0b libpangocairo-1.0-0 libx11-xcb1 libxcb-shm0 libxcursor1 libxi6 libxrender1 libxtst6 libsoup-3.0-0 gstreamer1.0-libav gstreamer1.0-plugins-base gstreamer1.0-plugins-good libegl1 libenchant-2-2 libepoxy0 libevdev2 libgles2 libglx0 libgstreamer-gl1.0-0 libgstreamer-plugins-base1.0-0 libgstreamer1.0-0 libgtk-4-1 libgudev-1.0-0 libharfbuzz-icu0 libhyphen0 libicu72 libjpeg62-turbo liblcms2-2 libmanette-0.2-0 libnotify4 libopengl0 libopenjp2-7 libopus0 libpng16-16 libproxy1v5 libsecret-1-0 libwayland-client0 libwayland-egl1 libwayland-server0 libwebp7 libwebpdemux2 libwoff1 libxml2 libxslt1.1 libatomic1 libevent-2.1-7 libavif16 xvfb fonts-noto-color-emoji fonts-unifont xfonts-scalable fonts-liberation fonts-ipafont-gothic fonts-wqy-zenhei fonts-tlwg-loma-otf fonts-freefont-ttf
|
RUN apt-get update && apt-get install -y nodejs npm xvfb
|
||||||
|
|
||||||
RUN playwright install-deps && playwright install
|
RUN playwright install-deps && playwright install
|
||||||
|
|
||||||
|
|
@ -37,4 +37,4 @@ RUN python manage.py collectstatic --noinput
|
||||||
#RUN python manage.py createcachetable django_cache
|
#RUN python manage.py createcachetable django_cache
|
||||||
|
|
||||||
# Use gunicorn on port 8000
|
# Use gunicorn on port 8000
|
||||||
CMD ["gunicorn", "--bind", ":8000", "--workers", "2", "django_project.wsgi"]
|
CMD ["gunicorn", "--bind", ":8000", "django_project.wsgi", "--timeout", "300"]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
# Generated by Django 5.1.2 on 2025-03-22 04:08
|
# Generated by Django 5.1.2 on 2025-03-28 04:43
|
||||||
|
|
||||||
|
import accounts.models
|
||||||
import django.contrib.auth.models
|
import django.contrib.auth.models
|
||||||
import django.contrib.auth.validators
|
import django.contrib.auth.validators
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
@ -48,8 +49,8 @@ class Migration(migrations.Migration):
|
||||||
name='FriendCode',
|
name='FriendCode',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('friend_code', models.CharField(max_length=19)),
|
('friend_code', models.CharField(max_length=19, validators=[accounts.models.validate_friend_code])),
|
||||||
('in_game_name', models.CharField(max_length=16)),
|
('in_game_name', models.CharField(max_length=14)),
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='friend_codes', to=settings.AUTH_USER_MODEL)),
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='friend_codes', to=settings.AUTH_USER_MODEL)),
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ def gravatar_url(email, size=20):
|
||||||
default = "retro"
|
default = "retro"
|
||||||
email_hash = gravatar_hash(email)
|
email_hash = gravatar_hash(email)
|
||||||
params = urlencode({'d': default, 's': str(size)})
|
params = urlencode({'d': default, 's': str(size)})
|
||||||
|
params = params.replace("&", "&")
|
||||||
return f"https://www.gravatar.com/avatar/{email_hash}?{params}"
|
return f"https://www.gravatar.com/avatar/{email_hash}?{params}"
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
|
|
@ -44,7 +45,7 @@ def gravatar(email, size=20):
|
||||||
"""
|
"""
|
||||||
url = gravatar_url(email, size)
|
url = gravatar_url(email, size)
|
||||||
# Return a safe HTML snippet with the image element
|
# Return a safe HTML snippet with the image element
|
||||||
html = f'<img src="{url}" width="{size}" height="{size}" alt="Gravatar">'
|
html = f'<img src="{url}" width="{size}" height="{size}" alt="Gravatar"></img>'
|
||||||
return mark_safe(html)
|
return mark_safe(html)
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
|
|
@ -55,7 +56,7 @@ def gravatar_no_hover(email, size=20):
|
||||||
"""
|
"""
|
||||||
url = gravatar_url(email, size)
|
url = gravatar_url(email, size)
|
||||||
# Return a safe HTML snippet with the image element
|
# Return a safe HTML snippet with the image element
|
||||||
html = f'<img src="{url}" width="{size}" height="{size}" alt="Gravatar" class="ignore">'
|
html = f'<img src="{url}" width="{size}" height="{size}" alt="Gravatar" class="ignore"></img>'
|
||||||
return mark_safe(html)
|
return mark_safe(html)
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% render_trade_offer dummy_trade_offer False False True %}
|
{% render_trade_offer dummy_trade_offer True %}
|
||||||
|
|
||||||
<div class="flex justify-between mt-4">
|
<div class="flex justify-between mt-4">
|
||||||
<button type="submit" name="edit" class="btn btn-secondary">Edit</button>
|
<button type="submit" name="edit" class="btn btn-secondary">Edit</button>
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,7 @@
|
||||||
<div class="container mx-auto max-w-2xl mt-6">
|
<div class="container mx-auto max-w-2xl mt-6">
|
||||||
<h2 class="text-2xl font-bold">Trade Offer Details</h2>
|
<h2 class="text-2xl font-bold">Trade Offer Details</h2>
|
||||||
<div class="flex justify-center mt-10">
|
<div class="flex justify-center mt-10">
|
||||||
{% if screenshot_mode == "true" %}
|
{% render_trade_offer object %}
|
||||||
{% render_trade_offer object True show_friend_code %}
|
|
||||||
{% else %}
|
|
||||||
{% render_trade_offer object False False True %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% if acceptance_form %}
|
{% if acceptance_form %}
|
||||||
<div class="w-3/4 mx-auto mt-4">
|
<div class="w-3/4 mx-auto mt-4">
|
||||||
|
|
|
||||||
146
theme/templatetags/trade_offer_png.html
Normal file
146
theme/templatetags/trade_offer_png.html
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
{% load gravatar card_badge tailwind_tags %}<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="{{base_url}}/static/css/dist/styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="trade-offer-card-screenshot p-4 h-full w-auto flex justify-center">
|
||||||
|
<div class="transition-all duration-500 trade-offer-card my-auto">
|
||||||
|
|
||||||
|
<!-- Flip container providing perspective -->
|
||||||
|
<div class="flip-container">
|
||||||
|
<!--
|
||||||
|
The rotating element (.flip-inner) now uses CSS Grid to stack its children in a single cell.
|
||||||
|
Persistent border, shadow, and rounding are applied here and the card rotates entirely.
|
||||||
|
-->
|
||||||
|
<div class="flip-inner freeze-bg-color grid grid-cols-1 grid-rows-1 card bg-base-100 card-border shadow-lg {% if num_cards_available >= 4 %}w-160{% else %}w-96 md:w-80 lg:w-96{% endif %} transform transition-transform duration-700 ease-in-out">
|
||||||
|
|
||||||
|
<!-- Front Face: Trade Offer -->
|
||||||
|
<!-- Using grid placement classes (col-start-1 row-start-1) ensures both faces overlap -->
|
||||||
|
<div class="flip-face front mb-2 col-start-1 row-start-1 grid grid-cols-1 auto-rows-min gap-2 content-between">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="flip-face-header self-start">
|
||||||
|
<a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block">
|
||||||
|
<!-- Set this container as relative to position the avatar absolutely -->
|
||||||
|
<div class="relative mt-6 mb-4 mx-2 sm:mx-4">
|
||||||
|
<!-- Two-column grid for the labels -->
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<span class="text-sm font-semibold text-center">Has</span>
|
||||||
|
<span class="text-sm font-semibold text-center">Wants</span>
|
||||||
|
</div>
|
||||||
|
<!-- The avatar is placed absolutely and centered -->
|
||||||
|
<div class="absolute inset-x-0 top-1/2 transform -translate-y-1/2 flex justify-center">
|
||||||
|
<div class="avatar">
|
||||||
|
<div class="w-10 rounded-full">
|
||||||
|
{{ initiated_by_email|gravatar:40 }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<!-- Main Trade Offer Row -->
|
||||||
|
<div class="flip-face-body self-start">
|
||||||
|
<a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block">
|
||||||
|
<div class="px-2 main-badges pb-0">
|
||||||
|
{% if num_cards_available >= 4 %}
|
||||||
|
<!-- When screenshot_mode is true, use an outer grid with 3 columns: Has side, a vertical divider, and Wants side -->
|
||||||
|
<div class="flex flex-row gap-2 justify-around">
|
||||||
|
<!-- Has Side (inner grid of 2 columns) -->
|
||||||
|
<div class="flex flex-row gap-2">
|
||||||
|
{% for card in have_cards_available|slice:"0:2" %}
|
||||||
|
{% card_badge card.card card.quantity %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<!-- Vertical Divider -->
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<div class="w-px bg-gray-300 h-full"></div>
|
||||||
|
</div>
|
||||||
|
<!-- Wants Side (inner grid of 2 columns) -->
|
||||||
|
<div class="flex flex-row gap-2">
|
||||||
|
{% for card in want_cards_available|slice:"0:2" %}
|
||||||
|
{% card_badge card.card card.quantity %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<!-- Normal mode: just use an outer grid with 2 columns -->
|
||||||
|
<div class="flex flex-row gap-2 justify-around">
|
||||||
|
<!-- Has Side -->
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
{% for card in have_cards_available|slice:"0:1" %}
|
||||||
|
{% card_badge card.card card.quantity %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<!-- Wants Side -->
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
{% for card in want_cards_available|slice:"0:1" %}
|
||||||
|
{% card_badge card.card card.quantity %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Extra Card Badges (Collapsible) -->
|
||||||
|
{% if num_cards_available >= 4 %}
|
||||||
|
<div class="px-2 extra-badges">
|
||||||
|
<!-- In screenshot mode, add a vertical divider between the Has and Wants sides -->
|
||||||
|
<div class="flex flex-row gap-2 justify-around">
|
||||||
|
<!-- Has Side Extra Badges -->
|
||||||
|
<div class="grid grid-cols-2 gap-2 {% if num_cards_available >= 4 %}w-[296px]{% endif %}">
|
||||||
|
{% for card in have_cards_available|slice:"2:" %}
|
||||||
|
{% card_badge card.card card.quantity %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<!-- Vertical Divider -->
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<div class="w-px bg-gray-300 h-full"></div>
|
||||||
|
</div>
|
||||||
|
<!-- Wants Side Extra Badges -->
|
||||||
|
<div class="grid grid-cols-2 gap-2 {% if num_cards_available >= 4 %}w-[296px]{% endif %}">
|
||||||
|
{% for card in want_cards_available|slice:"2:" %}
|
||||||
|
{% card_badge card.card card.quantity %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="px-2 extra-badges">
|
||||||
|
<a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block">
|
||||||
|
<div class="flex flex-row gap-2 justify-around">
|
||||||
|
<!-- Has Side Extra Badges -->
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
{% for card in have_cards_available|slice:"1:" %}
|
||||||
|
{% card_badge card.card card.quantity %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<!-- Wants Side Extra Badges -->
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
{% for card in want_cards_available|slice:"1:" %}
|
||||||
|
{% card_badge card.card card.quantity %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flip-face-footer self-end">
|
||||||
|
<div class="flex flex-col gap-2 text-center">
|
||||||
|
<div class="text-sm font-semibold text-base-content">
|
||||||
|
{{ in_game_name }} {% if show_friend_code %}<span class="text-base-content/50">•</span> {{ friend_code }}{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 5.1.2 on 2025-03-22 04:08
|
# Generated by Django 5.1.2 on 2025-03-28 04:43
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from django import template
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
@register.inclusion_tag('templatetags/trade_offer.html', takes_context=True)
|
@register.inclusion_tag('templatetags/trade_offer.html', takes_context=True)
|
||||||
def render_trade_offer(context, offer, screenshot_mode=False, show_friend_code=False, expanded=False):
|
def render_trade_offer(context, offer, expanded=False):
|
||||||
"""
|
"""
|
||||||
Renders a trade offer including detailed trade acceptance information.
|
Renders a trade offer including detailed trade acceptance information.
|
||||||
Freezes the through-model querysets to avoid extra DB hits.
|
Freezes the through-model querysets to avoid extra DB hits.
|
||||||
|
|
@ -34,10 +34,8 @@ def render_trade_offer(context, offer, screenshot_mode=False, show_friend_code=F
|
||||||
'acceptances': acceptances,
|
'acceptances': acceptances,
|
||||||
'have_cards_available': have_cards_available,
|
'have_cards_available': have_cards_available,
|
||||||
'want_cards_available': want_cards_available,
|
'want_cards_available': want_cards_available,
|
||||||
'screenshot_mode': screenshot_mode,
|
|
||||||
'in_game_name': offer.initiated_by.in_game_name,
|
'in_game_name': offer.initiated_by.in_game_name,
|
||||||
'friend_code': offer.initiated_by.friend_code,
|
'friend_code': offer.initiated_by.friend_code,
|
||||||
'show_friend_code': show_friend_code,
|
|
||||||
'num_cards_available': len(have_cards_available) + len(want_cards_available),
|
'num_cards_available': len(have_cards_available) + len(want_cards_available),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,3 +73,51 @@ def action_button_class(state_value):
|
||||||
}
|
}
|
||||||
# Return a default style if the state isn't in the mapping.
|
# Return a default style if the state isn't in the mapping.
|
||||||
return mapping.get(state_value, 'btn btn-outline')
|
return mapping.get(state_value, 'btn btn-outline')
|
||||||
|
|
||||||
|
@register.inclusion_tag('templatetags/trade_offer_png.html', takes_context=True)
|
||||||
|
def render_trade_offer_png(context, offer, show_friend_code=False):
|
||||||
|
trade_offer_have_cards = list(offer.trade_offer_have_cards.all())
|
||||||
|
trade_offer_want_cards = list(offer.trade_offer_want_cards.all())
|
||||||
|
|
||||||
|
have_cards_available = [
|
||||||
|
card for card in trade_offer_have_cards
|
||||||
|
if card.quantity > card.qty_accepted
|
||||||
|
]
|
||||||
|
want_cards_available = [
|
||||||
|
card for card in trade_offer_want_cards
|
||||||
|
if card.quantity > card.qty_accepted
|
||||||
|
]
|
||||||
|
|
||||||
|
num_cards = max(len(have_cards_available), len(want_cards_available))
|
||||||
|
aspect_ratio = 1.91
|
||||||
|
base_height = (round(num_cards / 2) * 56) + 138
|
||||||
|
if (len(have_cards_available) + len(want_cards_available)) >= 4:
|
||||||
|
base_width = (4 * 144) + 96
|
||||||
|
else:
|
||||||
|
base_width = (2 * 144) + 128
|
||||||
|
|
||||||
|
if base_height > base_width:
|
||||||
|
image_height = base_height
|
||||||
|
image_width = int(round(image_height * aspect_ratio)) + 1
|
||||||
|
else:
|
||||||
|
image_width = base_width
|
||||||
|
image_height = int(round(image_width / aspect_ratio))
|
||||||
|
|
||||||
|
base_url = context.get('request').build_absolute_uri('/')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'offer_pk': offer.pk,
|
||||||
|
'offer_hash': offer.hash,
|
||||||
|
'rarity_icon': offer.rarity_icon,
|
||||||
|
'initiated_by_email': offer.initiated_by.user.email,
|
||||||
|
'initiated_by_username': offer.initiated_by.user.username,
|
||||||
|
'have_cards_available': have_cards_available,
|
||||||
|
'want_cards_available': want_cards_available,
|
||||||
|
'in_game_name': offer.initiated_by.in_game_name,
|
||||||
|
'friend_code': offer.initiated_by.friend_code,
|
||||||
|
'show_friend_code': show_friend_code,
|
||||||
|
'num_cards_available': len(have_cards_available) + len(want_cards_available),
|
||||||
|
'image_width': image_width,
|
||||||
|
'image_height': image_height,
|
||||||
|
'base_url': base_url,
|
||||||
|
}
|
||||||
|
|
@ -599,83 +599,47 @@ class TradeAcceptanceUpdateView(LoginRequiredMixin, FriendCodeRequiredMixin, Upd
|
||||||
class TradeOfferPNGView(View):
|
class TradeOfferPNGView(View):
|
||||||
"""
|
"""
|
||||||
Generate a PNG screenshot of the rendered trade offer detail page using Playwright.
|
Generate a PNG screenshot of the rendered trade offer detail page using Playwright.
|
||||||
|
This view loads the SVG representation generated by the trade_offer_svg template tag,
|
||||||
This view loads the trade offer detail page, waits for the trade offer element to render,
|
wraps it in a minimal HTML document, and converts it to PNG using Playwright.
|
||||||
simulates a click to expand extra badges, and then screenshots only the trade offer element.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
# For demonstration purposes, get the first trade offer.
|
from django.shortcuts import get_object_or_404
|
||||||
# In production, you might want to identify the offer via a URL parameter.
|
trade_offer = get_object_or_404(TradeOffer, pk=kwargs['pk'])
|
||||||
trade_offer = TradeOffer.objects.get(pk=kwargs['pk'])
|
|
||||||
if not trade_offer:
|
|
||||||
raise Http404("Trade offer not found")
|
|
||||||
|
|
||||||
# Get the URL for the trade offer detail view.
|
from trades.templatetags import trade_offer_tags
|
||||||
detail_url = request.build_absolute_uri(
|
# Generate context for the SVG template tag.
|
||||||
reverse_lazy("trade_offer_detail", kwargs={"pk": trade_offer.pk})+"?screenshot_mode=true"
|
tag_context = trade_offer_tags.render_trade_offer_png(
|
||||||
|
{'request': request}, trade_offer, show_friend_code=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Use provided dimensions from the context
|
||||||
|
image_width = tag_context.get('image_width')
|
||||||
|
image_height = tag_context.get('image_height')
|
||||||
|
|
||||||
|
html = render_to_string("templatetags/trade_offer_png.html", tag_context)
|
||||||
|
|
||||||
|
# Launch Playwright to render the HTML and capture a screenshot.
|
||||||
with sync_playwright() as p:
|
with sync_playwright() as p:
|
||||||
browser = p.chromium.launch(headless=True)
|
browser = p.chromium.launch(
|
||||||
context = browser.new_context(
|
headless=True,
|
||||||
viewport={"width": 1280, "height": 800},
|
args=[
|
||||||
|
"--disable-gpu",
|
||||||
|
"--no-sandbox",
|
||||||
|
'--disable-setuid-sandbox',
|
||||||
|
'--disable-dev-shm-usage',
|
||||||
|
'--disable-accelerated-2d-canvas',
|
||||||
|
'--no-first-run',
|
||||||
|
'--disable-gpu'
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
context_browser = browser.new_context(viewport={"width": image_width, "height": image_height})
|
||||||
# If the request contains a Django session cookie,
|
page = context_browser.new_page()
|
||||||
# add it to the browser context to bypass the login screen.
|
page.set_content(html, wait_until="networkidle")
|
||||||
session_cookie = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
|
element = page.wait_for_selector(".trade-offer-card-screenshot")
|
||||||
if session_cookie:
|
screenshot_bytes = element.screenshot(type="png", omit_background=True)
|
||||||
cookie = {
|
|
||||||
"name": settings.SESSION_COOKIE_NAME,
|
|
||||||
"value": session_cookie,
|
|
||||||
"domain": request.get_host().split(':')[0],
|
|
||||||
"path": "/",
|
|
||||||
"httpOnly": True,
|
|
||||||
"secure": not settings.DEBUG,
|
|
||||||
}
|
|
||||||
context.add_cookies([cookie])
|
|
||||||
|
|
||||||
# Open a new page and navigate to the detail view.
|
|
||||||
page = context.new_page()
|
|
||||||
page.goto(detail_url, wait_until="networkidle")
|
|
||||||
|
|
||||||
# Inject CSS to force transparency.
|
|
||||||
page.add_style_tag(content="""
|
|
||||||
html, body, .bg-base-200 {
|
|
||||||
background-color: rgba(255, 255, 255, 0) !important;
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
|
|
||||||
trade_offer_selector = ".trade-offer-card-screenshot"
|
|
||||||
page.wait_for_selector(trade_offer_selector)
|
|
||||||
|
|
||||||
# Simulate a click on the toggle button within the trade offer element
|
|
||||||
# to force the extra details (e.g., extra badges) to expand.
|
|
||||||
# We use a selector that targets the first svg with a "cursor-pointer" class inside the trade offer card.
|
|
||||||
toggle_selector = f"{trade_offer_selector} svg.cursor-pointer"
|
|
||||||
try:
|
|
||||||
toggle = page.query_selector(toggle_selector)
|
|
||||||
if toggle:
|
|
||||||
toggle.click()
|
|
||||||
# Wait for the CSS animation to complete (600ms as in your template)
|
|
||||||
page.wait_for_timeout(600)
|
|
||||||
except Exception:
|
|
||||||
# If the toggle is not found or clicking fails, proceed without expansion.
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Locate the element containing the trade offer and capture its screenshot.
|
|
||||||
element = page.query_selector(trade_offer_selector)
|
|
||||||
if not element:
|
|
||||||
browser.close()
|
|
||||||
raise Http404("Trade offer element not found on page")
|
|
||||||
|
|
||||||
png_bytes = element.screenshot(type="png", omit_background=True)
|
|
||||||
browser.close()
|
browser.close()
|
||||||
|
|
||||||
return HttpResponse(png_bytes, content_type="image/png")
|
return HttpResponse(screenshot_bytes, content_type="image/png")
|
||||||
|
|
||||||
class TradeOfferCreateConfirmView(LoginRequiredMixin, View):
|
class TradeOfferCreateConfirmView(LoginRequiredMixin, View):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue