python
40 lines · 8 steps
Batch price updates safely in Django
Apply a percentage markup to every active product in a category using precise decimals, row locks, and batched writes inside one transaction.
Explained by
highlit
1from decimal import Decimal, ROUND_HALF_UP
2
3from django.db import transaction
4from django.db.models import F
5
6from catalog.models import Product
7
8
9def apply_category_markup(category_id, percent_increase, *, batch_size=500):
10 factor = Decimal(1) + (Decimal(percent_increase) / Decimal(100))
11 cent = Decimal("0.01")
12
13 products = (
14 Product.objects
15 .select_for_update()
16 .filter(category_id=category_id, is_active=True)
17 .only("id", "price", "list_price")
18 )
19
20 updated = []
21 with transaction.atomic():
22 for product in products.iterator(chunk_size=batch_size):
23 new_price = (product.price * factor).quantize(cent, rounding=ROUND_HALF_UP)
24 if new_price == product.price:
25 continue
26 product.price = new_price
27 product.list_price = max(new_price, product.list_price)
28 updated.append(product)
29
30 if len(updated) >= batch_size:
31 Product.objects.bulk_update(updated, ["price", "list_price"], batch_size=batch_size)
32 updated.clear()
33
34 if updated:
35 Product.objects.bulk_update(updated, ["price", "list_price"], batch_size=batch_size)
36
37 Product.objects.filter(category_id=category_id, is_active=True).update(
38 price_updated_at=F("updated_at"),
39 revision=F("revision") + 1,
40 )
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Use Decimal with explicit quantize and rounding to avoid float errors in money math.
- 2select_for_update plus a single atomic block prevents concurrent writers from corrupting price updates.
- 3Streaming with iterator and flushing bulk_update in batches keeps memory flat over large tables.
Related explainers
python
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status from pydantic import BaseModel, EmailStr from sqlalchemy.orm import Session
Building a signup endpoint in FastAPI
dependency-injection
request-validation
background-tasks
Intermediate
8 steps
php
<?php namespace App\Console\Commands;
Releasing stale document locks in Laravel
artisan-command
transactions
row-locking
Intermediate
6 steps
python
from django import forms from django.utils import timezone from .models import Reservation
Multi-field validation in a Django ModelForm
form validation
cross-field validation
modelform
Intermediate
7 steps
typescript
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable, catchError, concatMap, finalize } from 'rxjs'; import { DataSource, QueryRunner } from 'typeorm';
Wrapping requests in a transaction with NestJS
interceptors
transactions
rxjs
Advanced
7 steps
python
from django import template from django.urls import reverse, NoReverseMatch from django.utils.html import format_html
Active nav-link template tags in Django
template tags
url routing
active state
Intermediate
7 steps
python
from functools import wraps import asyncio from fastapi import APIRouter, FastAPI, Request
Per-route request timeouts in FastAPI
decorators
async
timeouts
Intermediate
6 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/batch-price-updates-safely-in-django-explained-python-db68/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.