Byg · Django

Render try-on-billeder fra en Django-worker

I én sætning

En Django-integration gemmer én række per job, indsender fra en Celery-task, poller hvert 3. sekund indtil statussen er completed, og downloader derefter filen til dit eget lager.

  • Credits: 5 credits ved 2K · 6 credits ved 4K
  • Typisk tid: 1.5–4 min
  • Behandling: async, polling

Hvert tal på denne side stammer fra Photta API-dokumentationen · tjekket

En model, der afspejler jobbet, en Celery-task, der indsender og poller, og lagerplads, der bevarer filen, efter leverings-URL'en er udløbet.

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

Sådan virker det

Én række, én task, én fil

Modellér jobbet, indsend fra en task, poll med backoff, gem filen, eksponér statussen.

  1. Trin 1

    Modellér jobbet

    Én række per generering: produktet, genererings-id'et, statussen og output-URL'en. Rækken er det, dine views læser; API'et er det, workeren læser.

  2. Trin 2

    Hent en Photta API-nøgle

    Læs den fra miljøet i settings, aldrig fra databasen, og brug en separat nøgle per miljø, så et staging-læk ikke kan bruge produktions-credits.

  3. Trin 3

    Indsend i en task

    Views bør returnere med det samme. Celery-tasken POSTer jobbet og skriver det returnerede id på rækken.

  4. Trin 4

    Poll med backoff

    Poll hvert 3. sekund, indtil statussen er completed eller failed. Ved en 429 skal du vente det antal sekunder, Retry-After angiver, i stedet for at prøve igen med det samme.

  5. Trin 5

    Gem filen permanent

    Download det færdige billede til dit eget lager. URL'en leverer ét job; din bucket er det, der serverer produktsiden i årevis.

  6. Trin 6

    Eksponér statussen

    Din frontend poller din række, ikke Photta API'et. Det holder nøglen på serveren og lader dig vise fremskridt præcis, som du vil.

Model, task, fejl, lagring

Indsend-og-poll samt fejlhåndteringen er gengivet fra 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"])

Spørgsmål udviklere stiller

Spørgsmål Django-udviklere stiller

Hvorfor en worker i stedet for et view?

En generering tager længere tid, end et request bør tage. En worker overlever en lukket fane i browseren, et deploy og et timeout; et view overlever ingen af delene.

Hvor længe bør tasken polle?

Længe nok til endpointets dokumenterede latenstid, med et loft. Eksemplerne her stopper efter et fast antal forsøg i stedet for at polle for evigt.

Kan jeg bruge asynkron Django i stedet for Celery?

Ja — kaldene er ren HTTP. Det afgørende er, at arbejdet lever længere end requestet, uanset hvordan du strukturerer det.

Hvordan tester jeg uden at bruge credits?

Optag ét reelt svar, og afspil det igen i tests. Ressource-endpoints og detaljefotoanalysen er gratis, så kontrol af nøgle og forbindelser koster ingenting.

Kan én nøgle bruges i alle miljøer?

Det kan den, men alle nøgler trækker på samme saldo, så et loop i staging dræner produktions-credits. Separate nøgler gør skaden synlig og nem at stoppe.

En worker, en række og en fil

Indsend-og-poll på denne side er gengivet fra API-dokumentationen.