From Python Script to Production: A Django Coaching Case Study
Working on something challenging? I coach developers 1:1 on the judgment behind the code, not just the syntax. How it works โ
Six weeks of 1:1 coaching. The output: a Django app in production on Fly.io, covering movies, anime, and manga, with user accounts, a save library, Docker, and CI/CD on every push. Daniele started with Python skills and a project idea. Here's what the work actually looked like.
The starting point
The idea was a platform to discover and track movies, anime, and manga. He had enough Python to start, already building a CLI tool with a swappable data layer in our first app together. What he didn't have was experience building a web app: the real mechanics of Django, how the pieces connect, what "ready to ship" means in practice.
Self-study can get you to a prototype. It won't tell you when code that works is teaching you the wrong habits. That's what weekly PR reviews are for.
Starting with discipline, not speed
The first PR was a Python script. It called the TMDB API, parsed the results, and displayed them. Functional and already a place to build habits.
For example: the type hint on _fetch_tmdb_data said -> dict, but the function could return None. Fix the type hint. The constants TMDB_URL and headers weren't uppercased consistently. Follow PEP8 conventions. The API key loaded from an .env file, but there was no .env-template telling other developers which variables to set. Add the template.
None of these changes affect whether the script runs. All of them affect whether another developer, or Daniele himself in six months, can reason about it. That's where professional developer habits form.
Django's machinery is yours to understand
Moving from a script to Django means the framework does a lot for you. The risk is accepting what it does for you without understanding its deeper workings.
In week 2, Daniele ran uv run ty check . and got two errors: Class 'Movie' has no attribute 'objects'. Django adds the objects manager dynamically at runtime; ty is a static type checker and can't see it. He asked the right question:
"So this was because of ty/type checker flagging an error? If we have a way to instrument ty globally to recognize Django's dynamic managers, that would be better. Do we need to pull in 'django-stubs' or a similar configuration?"
The answer was: Django's dynamic ORM creates real friction with static type checkers. The pragmatic fix was to explicitly declare objects: models.Manager on the model, making the implicit explicit for both the type checker and any developer reading the code. The question itself was the point. Understanding why the error existed led to a better solution.
We also made a note to compare with other type checkers like pyrefly (v1.0 just came out) or mypy and see if they have better Django support.
The same week, the movie detail page returned 404s even though data existed. The cause: movie_list fetched from the API but didn't persist to the database. movie_detail queried the database. Nothing matched. Daniele fixed the sync logic and wrote:
"I ran across a problem earlier that was caused by not having the DB in sync. So lesson learned and now I try not to forget to run it."
Running makemigrations after every model change now became a habit.
Refactoring is how architecture emerges
By week 6, the codebase had grown. Two API functions, get_movie_list_from_api and get_services_list_from_api, had identical try/except blocks. The only difference was the endpoint and the default return value.
Extracting a private _get_from_api(endpoint, default) helper isn't a trick. It's a principle: if two pieces of code do the same thing, one is a future bug waiting to diverge.
The refactor also cleaned up the return types from list[Movie] | None to list[Movie], replacing a None sentinel with a proper empty list default.
Each review surfaced one decision that sharpened his model of how good code behaves.
Week 6: Docker, CI/CD, and a live URL
Week 6 was Docker and Fly.io. The app that ran locally needed to run in the cloud: no SQLite disappearing on container restart (or moving to Postgres), environment variables properly set, static files served correctly, no secrets hardcoded anywhere, GitHub Actions deploying on every push to main (with passing tests).
Daniele learned a lot here and shipped his app:

When Daniele shared the deploy win in my coaching Slack, he put into words something that sits at the center of anybody wanting to improve their coding/developer skills now:
"Addressing quality and best practices and security and maintainability in your code does not pair well with velocity, especially when you're learning. So I preferred to improve my code quality and to become better at it by learning properly, postponing some feature release for later. In the process I learned Django and its main mechanics, Docker and deployment in the cloud."
The velocity-vs-quality tradeoff he named isn't unique to learning Django. It's the choice every developer makes every week. My coaching gave him the framework, the discipline and persistence were his.
Tutorials teach syntax. Courses teach patterns. AI gives unvetted code. None of them review your decisions on your code. That's what 1:1 coaching is for. Here's how it works โ