First off, South is indeed lacking in this area, but the new Django migrations are a more evolved version that solves this problem, so I'll explain how that solves it.
The new Django migrations are declarative, rather than procedural, and in addition each migration action is able to both modify the database schema as well as an in-memory version of the app's models. This means that any set of migrations implicitly has a schema defined - you can run through the migrations in memory to get the resulting schema.
However, of course, Django already has a definitive schema in apps' models.py files, whereas Rails takes the database as more of the source of truth. This is really what means there can be a stable schema defined for an app.
The combination of these, though, is what stops people forgetting to add migrations for things; you can't change the models without migrations noticing, as it can compare the concrete and derived schemas and work out what's changed. Django ignores what's actually in the database completely apart from a table which tracks which migrations have been applied; this way, anything you do out of scope hopefully fails hard and early rather than persisting till you set up a new environment or something.
Finally, a big part of Django migrations is reproduceability. I know that if I install the same app on two servers a few months apart, they'll have run through the exact same set of schema changes (and more importantly, any data migrations that are in the migration set that set up initial data, etc). Doing an initial schema load (like Django's syncdb) defeats this, and doesn't let you write data migrations but instead confines you to data loading being done separately and not the same way across installed and new instances.
There's some discussion about having a just-make-it-from-the-models-py-file mode for the migration backend for test setup (it can be faster), but this is only for very large apps; generally, I like having migrations run in testing too as it means your database is going to be exactly the same as the real one.
Finally, if you end up with too many migrations, there's an ability to squash them down into a few new migrations and have Django automatically manage the switchover (it also optimises away things like add/delete field pairs and things).
Django always has kept the schema in the code base - that's what models.py files _are_ - we just apply it differently to Rails.
(also, in future, django-developers is a better place for discussing migrations, as South is EOL)