# Building Durable, AI-Powered Workflows in Django

> Source: <https://ainexusdaily.vercel.app/article/2026-09-11-building-durable-ai-powered-workflows-in-django>
> Published: 2026-09-11 10:31:13+00:00

# Building Durable, AI-Powered Workflows in Django

This tutorial walks through a complete local development path for this project: Start the Django application and its dependencies with Docker Compose. Install Git pre-commit hooks so quality checks run before each commit. Create a product through the workflow API. Follow the product as Temporal calc

This tutorial walks through a complete local development path for this project: Start the Django application and its dependencies with Docker Compose. Install Git pre-commit hooks so quality checks run before each commit. Create a product through the workflow API. Follow the product as Temporal calculates a price, waits for approval, updates the price, and publishes the product. The intended reader is comfortable opening a terminal but new to Docker Compose and Temporal. You do not need to install Python or PostgreSQL directly on your computer: the local Compose setup runs those application dependencies in containers. The project is a Django application backed by PostgreSQL. Product onboarding is asynchronous, so the HTTP request does not perform every step itself. Instead, Django creates the product and starts a Temporal workflow. The local stack contains: Service Purpose Local address django Django development server and migrations http://localhost:8000 postgres Application database Internal Compose network temporal Temporal server localhost:7233 temporal-worker Executes workflow code and activities Internal Compose network temporal-ui Inspect workflow executions http://localhost:8088 mailpit Captures development email http://localhost:8025 The service definitions live in docker-compose.local.yml. Compose gives services stable names on its internal network, which is why Django connects to postgres and the worker connects to temporal instead of localhost. Install these tools first: Docker Desktop, including Docker Compose Git just, a command runner used by this repository Check that they are available: docker --version docker compose version git --version just --version Clone the repository and enter it: git clone https://github.com/klhenams/verbose-spork.git cd app The Compose file loads environment values from: .envs/.local/.django .envs/.local/.postgres The important local settings are: POSTGRES_HOST=postgres, because PostgreSQL is another Compose service. TEMPORAL_HOST=temporal:7233, supplied to the Django and worker containers. HUGGINGFACEHUB_API_TOKEN, which is optional for this tutorial. Do not commit real credentials or API tokens. If your checkout does not contain local environment files, create them from your development configuration. The Django file can contain an empty Hugging Face token while learning the workflow; the application has a deterministic cost-based fallback. The repository's justfile selects docker-compose.local.yml automatically: just up Under the hood, this runs: docker compose -f docker-compose.local.yml up -d --remove-orphans The first start may take a while because Docker builds the Django and PostgreSQL images and downloads Temporal images. The Django container runs python manage.py migrate before starting the development server, so the database schema is prepared automatically. Check the running containers: docker compose ps Open these pages in a browser: Django: http://localhost:8000 API documentation: http://localhost:8000/api/docs/ Temporal UI: http://localhost:8088 Mailpit: http://localhost:8025 If the application is still starting, inspect the logs: just logs django just logs temporal-worker The worker should eventually log that it is listening on the product-tasks task queue. A Temporal worker must be running for workflow activities to execute; starting only Django is not enough. pre-commit runs configured checks against staged files before Git creates a commit. This repository declares its hooks in .pre-commit-config.yaml and installs the development dependency from pyproject.toml. Install the hook inside the Django container so the host machine does not need the project's Python version: docker compose run --rm django pre-commit install Run all hooks once against the whole repository: docker compose run --rm django pre-commit run --all-files The configuration checks common file problems, applies Django upgrades, runs Ruff linting and formatting, formats pyproject.toml, and validates Django templates with djLint. Some hooks can modify files. Review those changes, stage them, and run the command again until it passes. To run the checks manually on only staged files: docker compose run --rm django pre-commit run The configuration deliberately excludes documentation and migrations from the hook selection. That does not mean documentation cannot be reviewed; it means the project avoids applying code-formatting hooks to those paths. The justfile provides a containerized pytest command: just pytest For the pricing activity tests only: just pytest app/products/tests/test_workflow_activities.py The pricing activity has two paths: With HUGGINGFACEHUB_API_TOKEN, it asks the configured Hugging Face model for a structured PriceSuggestion. Without a token, it uses a fallback of cost * 1.35, rounded to two decimal places. That fallback makes the local workflow usable without an external model account. The workflow-specific endpoint is registered as workflows in the product API router. Because the project includes the API router below /api/, create a product with: POST http://localhost:8000/api/workflows/ The endpoint requires authentication. Use the API documentation at http://localhost:8000/api/docs/ or obtain a DRF token from the project's authentication endpoint before sending the request. The request body should include the product fields below. category is an existing category ID: { "name": "Portable Monitor", "description": "A lightweight second screen for a laptop.", "sku": "MONITOR-PORTABLE-001", "price": "0.00", "cost": "100.00", "stock_quantity": 12, "low_stock_threshold": 3, "category": 1 } The initial price is allowed to be a placeholder. The workflow will calculate a suggestion and later write the selected price. The create view saves the product with pending_pricing and starts a workflow with the ID: product-onboarding-{product_id} For example, product 42 runs as product-onboarding-42 on Temporal task queue product-tasks. The workflow is implemented in app/products/workflows/workflows.py. Its activities are in app/products/workflows/activities.py. The worker executes calculate_suggested_market_price. With no Hugging Face token and a cost of 100.00, the fallback suggestion is: 100.00 * 1.35 = 135.00 GHS The activity returns a structured result containing the suggested price, currency, confidence score, reasoning, and source. If the external model fails, the activity logs the exception and uses the fallback instead. The API can query the running workflow without changing its state: GET http://localhost:8000/api/workflows/{product_id}/pricing-suggestion/ Example response: { "suggestion": { "suggested_price": 135.0, "recommended_margin": "35.0", "currency": "GHS", "confidence_score": 0.0, "reasoning": "Fallback pricing based on product cost.", "source": "cost_fallback" } } The workflow includes a short ten-second pause after pricing so the execution is easy to observe in the Temporal UI. That delay is for demonstration and is not required by the business process. The workflow waits for a signal named approve_suggested_price. Send the suggested price as-is or choose another value: POST http://localhost:8000/api/workflows/{product_id}/approve-price/ Request body: { "price": "129.99" } The API translates this request into a Temporal signal. The workflow records the selected price and continues. This is a useful distinction: A query reads workflow state. A signal sends an event that changes workflow state. If no approval arrives before the configured timeout, the workflow automatically selects the suggested price. The default timeout is 24 hours. After approval or timeout, update_product_price stores the final price in PostgreSQL. The product remains controlled by the workflow rather than being published immediately by the HTTP request. The workflow calls publish_product with a Temporal retry policy: Maximum of three attempts Two-second initial interval Exponential backoff with a coefficient of 2.0 When publishing succeeds, the activity changes the product status to active, and the workflow returns: { "status": "PUBLISHED", "final_price": 129.99, "was_auto_approved": false } To exercise the failure path, include the test-only field below when creating a product: { "name": "Failure Demo", "description": "Used to demonstrate workflow compensation.", "sku": "FAILURE-DEMO-001", "price": "0.00", "cost": "10.00", "stock_quantity": 1, "low_stock_threshold": 1, "category": 1, "test_should_fail_publish": true, "test_timeout_seconds": 20 } The publish activity raises an error deliberately. Temporal retries it three times. When all attempts fail, the workflow executes revert_product_status, which changes the database status to failed_publish, and returns a FAILED_PUBLISH result. This is a small Saga pattern: after earlier work has changed state, a compensation activity restores a meaningful failure state when a later operation cannot complete. The test_* fields are passed through by the current API view to make demonstrations and tests predictable. Treat them as development/testing controls, not as production business inputs. Open http://localhost:8088 and find the workflow named product-onboarding-{product_id}. You can inspect: The workflow status The activity attempts and retry history The waiting period for human approval The final return value The failure and compensation sequence This is often easier to understand than reading logs because Temporal shows the workflow's event history as a durable sequence. Stop the containers but keep the PostgreSQL volume: just down Stop the containers and delete local volumes, including the database data: just prune Use just prune carefully. The next just up starts with an empty database and will need to run migrations and recreate any sample data. Check the worker: just logs temporal-worker The worker must connect to temporal:7233 and register the product-tasks queue. If only the Django container is running, the workflow can start but its activities cannot be picked up. Inside Compose, use postgres as the database host, not localhost. Check that the database health check has passed: docker compose ps postgres That is expected when HUGGINGFACEHUB_API_TOKEN is empty or the model request fails. The cost-based fallback is intentionally part of the local implementation. Review and stage the change, then run the hook again: git diff git add -A docker compose run --rm django pre-commit run Docker Compose documentation Docker Compose networking pre-commit documentation Temporal Python SDK documentation Temporal workflow signals and queries Django REST framework ViewSets Django database migrations The central idea is simple: Django accepts the request and records intent, while Temporal coordinates the long-running work, human decision, retries, and compensation. Docker Compose makes that entire system reproducible on a developer laptop.

## Key Takeaways

- •This tutorial walks through a complete local development path for this project: Start the Django application and its dependencies with Docker Compose. Install Git pre-commit hooks so quality checks run before each commit. Create a product through the workflow API. Follow the product as Temporal calc
- •This story was reported by **Dev.to** , covering developments in the**dev** space.
- •AI advancements continue to reshape industries — read the full article on Dev.to for complete coverage.

📖 Continue reading the full article:

[Read Full Article on Dev.to →](https://dev.to/maaddae/building-durable-ai-powered-workflows-in-django-2dga)
