Build · Django

Try-on-Bilder über einen Django-Worker rendern

In aller Kürze

Eine Django-Integration speichert eine Zeile pro Job, übergibt ihn aus einem Celery-Task, pollt alle 3 Sekunden, bis der Status completed ist, und lädt die Datei anschließend in den eigenen Speicher herunter.

  • Credits: 5 Credits bei 2K · 6 Credits bei 4K
  • Typische Dauer: 1.5–4 min
  • Verarbeitung: asynchron, Polling

Jeder Zahlenwert auf dieser Seite stammt aus der Photta-API-Dokumentation · geprüft

Ein Model, das den Job abbildet, ein Celery-Task, der den Request sendet und pollt, sowie Storage, der die Datei speichert, nachdem die Auslieferungs-URL abgelaufen ist.

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

Funktionsweise

Eine Zeile, ein Task, eine Datei

Job modellieren, per Task submitten, mit Backoff pollen, Datei persistieren, Status bereitstellen.

  1. Schritt 1

    Job modellieren

    Eine Zeile pro Generierung: das Produkt, die Generierungs-ID, der Status und die Output-URL. Die Zeile wird von Ihren Views gelesen; die API liest der Worker aus.

  2. Schritt 2

    Photta-API-Key abrufen

    Lesen Sie ihn in den Settings aus der Umgebung aus, niemals aus der Datenbank. Nutzen Sie separate Keys pro Umgebung, damit ein Staging-Leak keine Production-credits verbraucht.

  3. Schritt 3

    In einem Task submitten

    Views sollten sofort antworten. Der Celery-Task sendet den Job per POST und schreibt die zurückgegebene ID in die Zeile.

  4. Schritt 4

    Mit Backoff pollen

    Alle 3 Sekunden pollen, bis der Status completed oder failed lautet. Bei einem 429 warten Sie die Retry-After-Sekunden ab, statt sofort einen neuen Request zu senden.

  5. Schritt 5

    Datei persistieren

    Laden Sie das fertige Bild in Ihren eigenen Storage herunter. Die URL liefert einen einzelnen Job aus; Ihr Bucket stellt das Bild über Jahre hinweg auf der Produktseite bereit.

  6. Schritt 6

    Status bereitstellen

    Ihr Frontend pollt Ihre Zeile, nicht die Photta-API. So bleibt der Key auf dem Server, und Sie können den Fortschritt beliebig visualisieren.

Model, Task, Errors, Storage

Der Submit-and-Poll-Ablauf und das Error-Handling stammen direkt aus der API-Dokumentation.

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

Häufige Fragen von Entwicklern

Fragen, die Django-Entwickler stellen

Warum ein Worker statt einer View?

Eine Generierung dauert länger, als ein Request offen bleiben sollte. Ein Worker übersteht geschlossene Browser-Tabs, Deploys und Timeouts – eine View übersteht keines davon.

Wie lange sollte der Task pollen?

Lange genug für die dokumentierte Latenz des Endpoints, aber mit einer Obergrenze. Die Beispiele hier stoppen nach einer festen Anzahl von Versuchen, anstatt endlos zu pollen.

Kann ich asynchrones Django statt Celery nutzen?

Ja — die Aufrufe sind einfaches HTTP. Entscheidend ist, dass die Ausführung den Request überdauert, unabhängig von der gewählten Architektur.

Wie teste ich, ohne credits zu verbrauchen?

Zeichnen Sie eine echte Response auf und spielen Sie diese in den Tests ab. Die Ressourcen-Endpoints und die Detailfoto-Analyse sind kostenlos; Key- und Konnektivitäts-Checks kosten also nichts.

Kann ein einziger Key für alle Umgebungen genutzt werden?

Technisch ja, aber jeder Key greift auf dasselbe Guthaben zu. Ein Loop auf dem Staging-System leert somit Production-credits. Getrennte Keys machen Vorfälle sofort sichtbar und leicht stoppbar.

Ein Worker, eine Zeile und eine Datei

Der Submit-and-Poll-Ablauf auf dieser Seite stammt direkt aus der API-Dokumentation.