Bangun · Django

Render gambar try-on dari worker Django

Ringkasan singkat

Integrasi Django menyimpan satu baris per job, melakukan submit dari task Celery, melakukan polling setiap 3 detik hingga statusnya completed, lalu mengunduh file ke penyimpanan Anda sendiri.

  • Credits: 5 credits pada 2K · 6 credits pada 4K
  • Waktu umum: 1.5–4 min
  • Pemrosesan: async, polling

Setiap angka di halaman ini bersumber dari dokumentasi API Photta · diperiksa

Model yang mencerminkan job, task Celery yang mengirimkan dan melakukan polling, serta penyimpanan yang mempertahankan file setelah delivery URL kedaluwarsa.

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

Cara kerja

Satu baris, satu task, satu file

Modelkan job, submit dari task, lakukan polling dengan backoff, simpan file secara persisten, ekspos statusnya.

  1. Langkah 1

    Modelkan job

    Satu baris per pembuatan: produk, id pembuatan, status, dan URL output. Baris tersebut dibaca oleh views Anda; API dibaca oleh worker.

  2. Langkah 2

    Dapatkan API key Photta

    Baca dari environment di settings, jangan pernah dari database, dan gunakan key terpisah per environment agar kebocoran di staging tidak menghabiskan credits produksi.

  3. Langkah 3

    Submit dalam sebuah task

    Views harus langsung mengembalikan respons. Task Celery melakukan POST job dan menulis id yang dikembalikan ke dalam baris.

  4. Langkah 4

    Lakukan polling dengan backoff

    Lakukan polling setiap 3 detik hingga statusnya completed atau failed. Pada status 429, tunggu durasi detik Retry-After alih-alih langsung mencoba ulang.

  5. Langkah 5

    Simpan file secara persisten

    Unduh gambar yang sudah selesai ke penyimpanan Anda sendiri. URL tersebut mengirimkan satu job; bucket Anda yang akan menayangkannya di halaman produk selama bertahun-tahun.

  6. Langkah 6

    Ekspos statusnya

    Front-end Anda melakukan polling ke baris database Anda, bukan ke API Photta. Ini menjaga key tetap aman di server dan memungkinkan Anda menampilkan progres sesuai keinginan.

Model, task, error, penyimpanan

Alur submit-and-poll dan penanganan error dirender dari dokumentasi API.

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

Pertanyaan umum developer

Pertanyaan yang sering diajukan developer Django

Mengapa menggunakan worker alih-alih view?

Proses pembuatan memakan waktu lebih lama dari batas waktu request yang ideal. Worker tetap bertahan meski tab browser ditutup, ada deploy, atau terjadi timeout; view tidak dapat bertahan dari kondisi tersebut.

Berapa lama task harus melakukan polling?

Cukup lama sesuai latensi endpoint yang terdokumentasi, dengan batas maksimal. Contoh di sini berhenti setelah jumlah percobaan tertentu alih-alih melakukan polling tanpa henti.

Bisakah saya menggunakan Django async alih-alih Celery?

Bisa — pemanggilannya menggunakan HTTP biasa. Yang terpenting adalah proses kerja tetap berjalan melampaui siklus request, apa pun cara yang Anda gunakan.

Bagaimana cara menguji tanpa menghabiskan credits?

Rekam satu respons nyata dan putar ulang dalam pengujian. Endpoint resource dan analisis foto detail bersifat gratis, sehingga pemeriksaan key dan konektivitas tidak memerlukan biaya.

Bisakah satu key digunakan untuk semua environment?

Bisa, tetapi setiap key memotong saldo yang sama, sehingga loop di staging dapat menguras credits produksi. Key yang terpisah membuat dampak lebih terlihat dan mudah dihentikan.

Sebuah worker, satu baris, dan satu file

Alur submit-and-poll pada halaman ini dirender dari dokumentasi API.