Skip to content

FTM301 · Examination paper

120 minutes · laptop with the repo · open repo, no internet · 40 marks · pass 20

Answer in ANSWERS.md with file paths and one sentence of mechanism per answer. A path without a mechanism scores half.


Section A — where does this live? (18 marks, 12 × 1.5)

Section titled “Section A — where does this live? (18 marks, 12 × 1.5)”

For each, give the file and the mechanism in one sentence.

  1. Where is the dashboard’s base template?
  2. Where is the Ninja API mounted, and in which file?
  3. Where are outbound emails rendered from?
  4. Which file decides whether an inbound mail came from a band or a client?
  5. Which field is generated rather than stored, and from what?
  6. Where is the arq worker’s entry module?
  7. Which function enqueues an arq job from Django?
  8. Where does the dashboard’s session middleware sit?
  9. Where do automated admin pages get their URLs?
  10. Which module holds S3 storage configuration?
  11. Where does the OpenAPI snapshot live?
  12. Which file holds the per-environment secrets?

Section B — trace one request (12 marks)

Section titled “Section B — trace one request (12 marks)”

A logged-in musician opens /pandemoniums/messages/2806551/ on musicians.staging.fixthemusic.com, and a message renders.

Produce the chain: URLconf entry → view → queryset/models touched → template. For each hop, one line of code (file:line) that proves it. Then name two queries you would expect the page to issue and how you would cut them to one. (6 + 6)


Section C — migration safety review (10 marks)

Section titled “Section C — migration safety review (10 marks)”

Given this migration on a live table with ~2M rows and running code:

class Migration(migrations.Migration):
operations = [
migrations.AddField(
model_name='enquirymessage',
name='priority',
field=models.IntegerField(default=0, null=False),
),
migrations.AlterField(
model_name='enquirymessage',
name='kind',
field=models.CharField(max_length=4, choices=KIND_CHOICES, null=True),
),
]

State every risk, whether the order matters, and what you would change. Name the deploy sequence for the kind widening.


SectionMarks
A1.5 each — 0.75 file, 0.75 mechanism. Round down
B6 chain, 6 queries. Chain: 1.5 per correct hop. Queries: 3 each for the two queries and the join/prefetch fix
C4 risks, 2 order, 4 sequence

Pass 20/40. Distinction 28. Section A must score at least 10 — this module is navigation, and a student who cannot find things has not learned it.

Expected answers:

  • 1 bands/templates/dashboard/layout.html
  • 2 ftm/urls.py, include('bands.api_v2_urls') at musician-api/v0/
  • 3 emails/templates/bookings/enquiryband_correspondence/quote.html and to_client.html, rendered from emails/
  • 4 emails/views.py → find_side()
  • 5 EnquiryMessage.side — GeneratedField from to and kind
  • 6 worker/src/ftm_worker/__init__.py
  • 7 ftm/arq_client.py → enqueue_job_sync
  • 8 bands/dashboard/middleware.py / api_auth_middleware.py, ordered in settings
  • 9 get_urls() on a ModelAdmin, templates under booking/templates/admin/
  • 10 ftm/storage.py
  • 11 bands/api_v2/openapi_snapshot.json
  • 12 deployment/backend-env.nix
  • B ftm/urls.py → bands/dashboard/message_views → EnquiryMessage filtered by band + select_related on enquiry → bands/templates/dashboard/…. N+1 on band/enquiry is the query to cut.
  • C AddField(default=0, null=False) on 2M rows rewrites the table on older Postgres and takes a lock; AlterField widening a CharField is metadata-only in Postgres (no rewrite) but the choices change is code-side and the old rows must still parse; order matters because the add should be nullable + backfill + SET NOT NULL; the deploy sequence is add-nullable → migrate → deploy writing both → backfill → constrain.