Bygg · Django

Generer try-on-bilder fra en Django-worker

I én setning

En Django-integrasjon lagrer én rad per jobb, sender inn fra en Celery-oppgave, poller hvert 3. sekund til statusen er completed, og laster deretter ned filen til din egen lagringsplass.

  • Credits: 5 credits ved 2K · 6 credits ved 4K
  • Typisk tidsbruk: 1.5–4 min
  • Prosessering: asynkron, polling

Hvert tall på denne siden er hentet fra dokumentasjonen for Photta-API-et · verifisert

En modell som speiler jobben, en Celery-task som sender inn og poller, og lagring som beholder filen etter at leverings-URL-en er borte.

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')

Slik fungerer det

Én rad, én oppgave, én fil

Modeller jobben, send inn fra en oppgave, poll med backoff, lagre filen, eksponer statusen.

  1. Trinn 1

    Modeller jobben

    Én rad per generering: produktet, genererings-id-en, statusen og output-URL-en. Raden er det visningene dine leser; API-et er det workeren leser.

  2. Trinn 2

    Skaff en Photta-API-nøkkel

    Les den fra miljøet i innstillingene, aldri fra databasen, og bruk en separat nøkkel per miljø slik at en lekkasje i staging ikke kan bruke opp produksjons-credits.

  3. Trinn 3

    Send inn i en oppgave

    Visninger bør returnere umiddelbart. Celery-oppgaven poster jobben med POST og skriver den returnerte id-en til raden.

  4. Trinn 4

    Poll med backoff

    Poll hvert 3. sekund til statusen er completed eller failed. Ved en 429, vent antall sekunder angitt i Retry-After i stedet for å prøve igjen med en gang.

  5. Trinn 5

    Lagre filen

    Last ned det ferdige bildet til din egen lagringsplass. URL-en leverer én jobb; bucketen din er det som server produktsiden i årevis.

  6. Trinn 6

    Eksponer statusen

    Frontend-en din poller raden din, ikke Photta-API-et. Det holder nøkkelen på serveren og lar deg vise fremdrift akkurat slik du vil.

Modell, oppgave, feil, lagring

Innsending-og-polling og feilhåndteringen er gjengitt fra API-dokumentasjonen.

# 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"])

Spørsmål utviklere stiller

Spørsmål Django-utviklere stiller

Hvorfor en worker i stedet for en view?

En generering tar lengre tid enn en forespørsel bør ta. En worker overlever en lukket nettleserfane, en deploy og en tidsavbruddsfeil; en view overlever ingen av delene.

Hvor lenge bør oppgaven polle?

Lenge nok til å dekke endepunktets dokumenterte latenstid, med et øvre tak. Eksemplene her stopper etter et fast antall forsøk i stedet for å polle evig.

Kan jeg bruke asynkron Django i stedet for Celery?

Ja — kallene er ren HTTP. Det som betyr noe, er at arbeidet lever lenger enn forespørselen, uansett hvordan du organiserer det.

Hvordan tester jeg uten å bruke credits?

Ta opp én reell respons og spill den av i testene. Ressursendepunktene og detaljfotoanalysen er gratis, så nøkkel- og tilkoblingssjekker koster ingenting.

Kan én nøkkel betjene alle miljøer?

Det kan den, men alle nøkler trekker fra samme saldo, så en løkke i staging tømmer produksjons-credits. Separate nøkler gjør skaden synlig og enkel å stoppe.

En worker, en rad og en fil

Innsending-og-polling på denne siden er gjengitt fra API-dokumentasjonen.