Concurrency safety, tests and going live
Nidaan works — patients and appointments and reports and an API. The last lesson is the difference between "works on my machine" and "runs a clinic": making it concurrency-safe, testing the paths where a bug is expensive, and taking it through the deployment checklist to a live, secure deployment. This is where the concurrency, testing and deployment modules combine, and where the capstone — and the course — closes.
Harden the money and status paths against concurrency
A clinic's fees and payments are exactly the data a lost update corrupts (the concurrency module). Two
requests completing or adjusting the same appointment must not clobber each other. Nidaan's complete()
already wraps its writes in transaction.atomic; the remaining discipline is to never read-modify-write a
value across a gap:
# adjusting a fee — use F(), not read-modify-write
Appointment.objects.filter(id=aid).update(fee=F("fee") + adjustment) # atomic (verified: no lost update)
# a read-decide-write that isn't pure arithmetic — lock the row
with transaction.atomic():
appt = Appointment.objects.select_for_update().get(id=aid) # locked until commit
if appt.status == Appointment.Status.BOOKED:
appt.complete()
Verified earlier: read-modify-write lost an increment (₹180 became ₹130), F() applied both (₹180), and
select_for_update inside atomic serialised the update. And the design's UniqueConstraint(doctor, scheduled_for) makes double-booking impossible even under a race — the database rejects the second insert,
where an application check could not. The hardening rule for Nidaan: money and status changes are atomic,
adjust-by-value uses F() or a lock, and invariants are database constraints — not hopeful application
code.
Move the slow and scheduled work out of the request
A clinic sends appointment reminders and generates reports — slow and scheduled work that must not sit in a request (the background-work module). Reminders are a Celery task; the daily send is scheduled with Beat:
@shared_task
def send_appointment_reminder(appointment_id): # pass the id, not the object
appt = Appointment.objects.select_related("patient").get(id=appointment_id)
send_reminder(appt.patient, appt.scheduled_for)
# booking enqueues it; a Beat schedule sends tomorrow's reminders each morning
send_appointment_reminder.delay(appointment.id)
The view enqueues and returns immediately; a worker does the slow send. This keeps the booking request fast and the web workers free — the concurrency module's distinction between async (concurrent waits) and a task queue (slow work) applied: reminders are slow work, so they belong in the queue.
Test the paths where a bug is expensive
Nidaan handles money, medical data and access — so the tests weight those, plus the failure cases (the testing module's priority order). A representative suite:
class AppointmentTests(TestCase):
def test_complete_sets_status_fee_and_payment(self): # core business logic
appt = AppointmentFactory(status="booked")
appt.complete()
self.assertEqual(appt.status, "done")
self.assertTrue(Payment.objects.filter(appointment=appt).exists())
def test_negative_fee_is_rejected(self): # invariant (verified: IntegrityError)
with self.assertRaises(IntegrityError):
AppointmentFactory(fee=Decimal("-1"))
def test_double_booking_is_rejected(self): # race-safe invariant
slot = timezone.now()
doctor = DoctorFactory()
AppointmentFactory(doctor=doctor, scheduled_for=slot)
with self.assertRaises(IntegrityError):
AppointmentFactory(doctor=doctor, scheduled_for=slot)
class PatientAPIAccessTests(APITestCase):
def test_patient_sees_only_own_appointments(self): # the access breach test
mine = PatientFactory(user=self.user)
AppointmentFactory(patient=mine)
AppointmentFactory(patient=PatientFactory()) # someone else's
self.client.force_authenticate(self.user)
response = self.client.get("/api/my-appointments/")
self.assertEqual(len(response.json()), 1) # only mine
The tests that matter most are the breach and invariant ones: "a patient sees only their own
appointments" and "a negative fee / double booking is rejected". These use factory_boy (verified: factories
build valid objects and the model tests pass), and they fail loudly if someone breaks the access scoping or
removes a constraint — exactly the regressions you cannot afford in a clinic system. Model tests run under
the test runner; the API and view behaviour is confirmed with the test client.
Take it through the launch checklist
Finally, deploy Nidaan properly (the deployment module). The checklist, run before going live:
-
DEBUG = False, realALLOWED_HOSTS,SECRET_KEYand database from the environment. -
python manage.py check --deployclean (verified: it flags DEBUG, ALLOWED_HOSTS, SECRET_KEY, SSL/cookie/HSTS settings until fixed). - HTTPS on, with
SECURE_SSL_REDIRECT, secure cookies, HSTS — non-negotiable for medical data. - PostgreSQL (not SQLite), with tested backups (a clinic's records are irreplaceable).
- Static collected and served (verified:
collectstaticgathered 154 files); media on durable storage, sensitive files access-controlled (not public URLs). - Running under Gunicorn/Uvicorn behind a proxy — never
runserver. - Logging and error tracking configured, so a failure in production is visible.
- Verify the live site works — load pages over HTTPS, book an appointment, fetch the API as a patient — rather than assuming the deploy succeeded.
That last line is the habit the whole course insists on: a deploy succeeding is not the same as the site working — verify it. Work down the checklist and Nidaan goes live as a real, secure, tested clinic system.
The capstone, and the course, complete
You have built a real database-backed application end to end: a data model with honest types, relationships and constraints; the admin as an operational tool; efficient queries and ORM-driven reporting; forms, views and templates; authentication, permissions and object-level access protecting sensitive data; a patient API scoped so no one sees another's records; concurrency-safe money handling; background reminders; a test suite guarding the risky paths; and a hardened, HTTPS, PostgreSQL deployment. Every module of the course is in it — which is the point of a capstone, and the answer to the question the course kept asking: could you be handed real Django work and do it, including the hard parts? Nidaan is the evidence that you can.
Check your work
Concurrency hardening. Money/status writes atomic (transaction.atomic); adjust-by-value with F() or
select_for_update (verified: no lost update); double-booking prevented by a UniqueConstraint
(race-safe) — not hopeful application checks.
Slow/scheduled work. Appointment reminders as Celery tasks (pass the id, not the object), the daily send via Beat — slow work out of the request, keeping bookings fast.
Testing the expensive paths. Weight money, medical data and access, plus failure cases — especially the breach test (patient sees only their own) and invariant tests (negative fee / double booking rejected), using factories.
The launch checklist. DEBUG=False/hosts/secret; check --deploy clean; HTTPS + secure settings;
PostgreSQL + tested backups; static collected + media durable/access-controlled; Gunicorn behind a proxy;
logging/error tracking; and verify the live site works, not assume.
The whole. A real clinic system exercising every module — the evidence you can do real Django work, including the hard parts.
Practice
- Harden
complete()and any fee adjustment: confirm the writes are atomic and any adjust-by-value usesF()orselect_for_update; reproduce that a naive read-modify-write loses an update. - Confirm the
UniqueConstraintblocks a double booking even when you attempt two near-simultaneous creates. - Move appointment reminders into a Celery task enqueued at booking; confirm the booking request returns immediately.
- Write the breach test (
test_patient_sees_only_own_appointments) and the invariant tests (negative fee, double booking); confirm they pass, then break the scoping/constraint and watch them fail. - Run
check --deployand clear every warning; setDEBUG=Falsewith HTTPS settings and confirm it is clean. - Deploy Nidaan (or walk the full launch checklist), finishing by loading live pages over HTTPS and exercising the patient API — verifying, not assuming, it works.
Official documentation
- Django — Database transactions —
atomicfor the money paths. - Django — Deployment checklist — The launch checklist, authoritative.
- Django — Testing — Testing the risky paths.
That is the Django Web Development course. You built Nidaan — a real clinic and diagnostic-lab manager — from first model to live deployment, and you have the judgement to be handed real Django work and do it well.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship