Bygg · Django

Rendera try-on-bilder från en Django-worker

I en mening

En Django-integration sparar en rad per jobb, skickar från en Celery-task, pollar var 3:e sekund tills status är completed, och laddar sedan ned filen till din egen lagring.

  • Credits: 5 credits vid 2K · 6 credits vid 4K
  • Typisk tid: 1.5–4 min
  • Bearbetning: asynkron, polling

Varje siffra på den här sidan kommer från Photter API-dokumentationen · kontrollerad

En modell som speglar jobbet, en Celery-task som skickar och genomför polling samt lagring som behåller filen efter att leverans-URL:en har försvunnit.

RESPONSE=$(curl -s -X POST https://api.photta.app/api/v1/tryon/apparel \
  -H "Authorization: Bearer $PHOTTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "product_type": "top",
  "product_images": [
    "https://cdn.yourstore.com/products/shirt-front.jpg"
  ],
  "product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
  "product_category": "t_shirt",
  "mannequin_id": "mnk_f3a9b1",
  "pose_id": "pose_c2d4e5",
  "background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
  "lock_background": true,
  "resolution": "2K",
  "aspect_ratio": "3:4",
  "body_size": "M",
  "expression": "neutral",
  "user_instruction": "Keep the collar pattern crisp and centered."
}')
ID=$(echo "$RESPONSE" | jq -r '.data.id')

Hur det fungerar

En rad, en task, en fil

Modellera jobbet, skicka från en task, polla med backoff, spara filen, exponera statusen.

  1. Steg 1

    Modellera jobbet

    En rad per generering: produkten, genererings-id, status och output-URL. Raden är vad dina views läser; API:et är vad din worker läser.

  2. Steg 2

    Hämta en Photta API-nyckel

    Läs den från miljön i settings, aldrig från databasen, och använd en separat nyckel per miljö så att en läcka i staging inte kan förbruka produktions-credits.

  3. Steg 3

    Skicka i en task

    Views bör svara direkt. Celery-tasken gör en POST av jobbet och skriver det returnerade id:t till raden.

  4. Steg 4

    Polla med backoff

    Polla var 3:e sekund tills status är completed eller failed. Vid 429, vänta antalet sekunder i Retry-After istället för att försöka igen omedelbart.

  5. Steg 5

    Spara filen

    Ladda ned den färdiga bilden till din egen lagring. URL:en levererar ett jobb; din bucket är det som servar produktsidan i flera år.

  6. Steg 6

    Exponera statusen

    Ditt gränssnitt pollar din rad, inte Photta API:et. Det håller nyckeln på servern och låter dig visa förloppet precis som du vill.

Modell, task, fel, lagring

Submit-and-poll och felhanteringen genereras från API-dokumentationen.

# store/models.py
from django.db import models

class TryOn(models.Model):
    product = models.ForeignKey("Product", on_delete=models.CASCADE)
    generation_id = models.CharField(max_length=64, db_index=True)
    status = models.CharField(max_length=16, default="processing")
    output_url = models.URLField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
# store/tasks.py
from celery import shared_task

@shared_task(bind=True, max_retries=3)
def run_try_on(self, try_on_id):
    import os, requests
    
    res = requests.post(
        "https://api.photta.app/api/v1/tryon/apparel",
        headers={"Authorization": f"Bearer {os.environ['PHOTTA_API_KEY']}"},
        json={
          "product_type": "top",
          "product_images": [
            "https://cdn.yourstore.com/products/shirt-front.jpg"
          ],
          "product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
          "product_category": "t_shirt",
          "mannequin_id": "mnk_f3a9b1",
          "pose_id": "pose_c2d4e5",
          "background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
          "lock_background": True,
          "resolution": "2K",
          "aspect_ratio": "3:4",
          "body_size": "M",
          "expression": "neutral",
          "user_instruction": "Keep the collar pattern crisp and centered."
        },
    )
    data = res.json()["data"]  # data["id"], data["status"]
    
    import time
    
    def wait_for(job_id):
        for _ in range(120):
            res = requests.get(f"https://api.photta.app/api/v1/tryon/apparel/{job_id}", headers={"Authorization": f"Bearer {os.environ['PHOTTA_API_KEY']}"})
            data = res.json()["data"]
            if data["status"] == "completed":
                return data["output_url"]
            if data["status"] == "failed":
                raise RuntimeError(data.get("error_message", "generation failed"))
            time.sleep(3)
        raise TimeoutError("timed out after 6 minutes")
try:
    photta("POST", "/tryon/apparel", json={
      "product_type": "top",
      "product_images": [
        "https://cdn.yourstore.com/products/shirt-front.jpg"
      ],
      "product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
      "product_category": "t_shirt",
      "mannequin_id": "mnk_f3a9b1",
      "pose_id": "pose_c2d4e5",
      "background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
      "lock_background": True,
      "resolution": "2K",
      "aspect_ratio": "3:4",
      "body_size": "M",
      "expression": "neutral",
      "user_instruction": "Keep the collar pattern crisp and centered."
    })
except PhottaError as e:
    if e.status == 402:
        print("insufficient_credits_error: top up before retrying")
    elif e.status == 429:
        time.sleep(int(e.retry_after or 30))
    else:
        raise
# store/tasks.py — the output URL delivers one job; your storage keeps it
import requests
from django.core.files.base import ContentFile

def persist(try_on, output_url):
    body = requests.get(output_url, timeout=60).content
    try_on.product.gallery.save(f"{try_on.generation_id}.jpg", ContentFile(body))
    try_on.status, try_on.output_url = "completed", output_url
    try_on.save(update_fields=["status", "output_url"])

Vanliga utvecklarfrågor

Frågor som Django-utvecklare ställer

Varför en worker istället för en view?

En generering tar längre tid än en request bör ta. En worker överlever en stängd webbläsarflik, en driftsättning och en timeout; en view överlever inget av dem.

Hur länge ska tasken polla?

Tillräckligt länge för endpointens dokumenterade latens, med ett tak. Exemplen här avbryter efter ett fast antal försök istället för att polla i all oändlighet.

Kan jag använda asynkron Django istället för Celery?

Ja — anropen är vanlig HTTP. Det viktiga är att arbetet överlever requesten, oavsett hur du strukturerar det.

Hur testar jag utan att förbruka credits?

Spela in ett riktigt svar och återuppspela det i testerna. Resurs-endpoints och detaljfotoanalysen är gratis, så nyckel- och anslutningskontroller kostar ingenting.

Kan en nyckel användas i alla miljöer?

Det går, men varje nyckel förbrukar samma saldo, så en loop i staging tömmer dina produktions-credits. Separata nycklar gör skadan synlig och enkel att stoppa.

En worker, en rad och en fil

Submit-and-poll på den här sidan genereras från API-dokumentationen.