├── .gitignore ├── PITCHME.md ├── README.md ├── assets ├── Untitled.graffle ├── auto-complete.png ├── chess1.png ├── chess2.png └── django-migrations-ppt │ ├── basic.png │ ├── check-before-commit.png │ ├── chess.png │ ├── django-migrations-usage.png │ ├── django-orm.png │ └── fields.png ├── django-migrations-ppt.graffle ├── data.plist ├── image1.png └── image2.png ├── drafts.md ├── example ├── app1 │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_remove_person_intn.py │ │ ├── 0003_remove_person_intc.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── example │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── manage.py └── south_example ├── __init__.py ├── app1 ├── __init__.py ├── migrations │ ├── 0001_initial.py │ ├── 0003_auto__add_field_knight_age.py │ ├── 0004_auto__add_field_knight_foo.py │ ├── 0005_auto__del_field_knight_foo.py │ ├── 0006_auto__del_field_knight_age.py │ ├── 0007_auto__add_field_knight_name.py │ └── __init__.py ├── models.py ├── tests.py └── views.py ├── manage.py ├── settings.py └── urls.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /PITCHME.md: -------------------------------------------------------------------------------- 1 | ## Django Migrations Under the Hood 2 | 3 | ### By laixintao 4 | 5 | --- 6 | 7 | ## 关于我 8 | 9 | 10 | - 《捕蛇者说》FM(@laike9m @Manjusaka @Adam) 11 | - 《Python并行编程》翻译(还没完成) 12 | - pingtop, git-ext, iredis, etc. 13 | - 从2016年开始写 Python (Django1.8) 14 | 15 | ![auto-complete](assets/auto-complete.png) 16 | 17 | +++ 18 | 19 | ## 第一个 Django 项目 20 | 21 | - 一个人完成开发、部署、前后端; 22 | - 包括注册登录、活动发布、报名付款,通知; 23 | - 仍在线上运行(3年); 24 | 25 | +++ 26 | 27 | ## 但是... 28 | ### 其实我不会SQL... 29 | 30 | +++ 31 | 32 | - `python manage.py makemigrations` & `python manage.py migrate` for DDL 33 | - `python manage.py shell` for query and DML 34 | 35 | +++ 36 | 37 | ## 这就是 Django ! 38 | 39 | > The web framework for perfectionists with deadlines. 40 | 41 | ---?color=#ffcfdf 42 | 43 | ## Table of Contents 44 | 45 | - 👉 Django Migraiton 的功能 46 | - 工作原理 47 | - 用法和常见问题 48 | - Django 的选择 49 | 50 | +++ 51 | 52 | ## Migration 是做什么的? 53 | 54 | 1. Django 负责 CURD 55 | 2. 数据存储在 MySQL(或者其他SQL数据库中) 56 | 3. Django ORM 负责数据库的 Table 和 Python Class 对应 57 | 4. Migrations 就是数据库结构的 Version Control System! 58 | 59 | +++ 60 | 61 | ![](./assets/django-migrations-ppt/django-migrations-usage.png) 62 | 63 | ```python 64 | class Person(models.Model): 65 | name = models.CharField(max_length=64) 66 | age = models.IntegerField() 67 | ``` 68 | 69 | 映射到... 70 | 71 | ```SQL 72 | mysql root@localhost:django_example> describe app1_person; 73 | +-------+-------------+------+-----+---------+----------------+ 74 | | Field | Type | Null | Key | Default | Extra | 75 | +-------+-------------+------+-----+---------+----------------+ 76 | | id | int(11) | NO | PRI | | auto_increment | 77 | | name | varchar(64) | NO | | | | 78 | +-------+-------------+------+-----+---------+----------------+ 79 | ``` 80 | 81 | +++ 82 | 83 | ### 为什么需要 migrations? 84 | 85 | - 如果没有数据库结构迁移机制的话,需要手动在代码和表结构之间同步; 86 | - 团队协作会很混乱; 87 | - 多个环境部署会很混乱; 88 | - 手动变更难以追踪; 89 | 90 | +++ 91 | 92 | ### Django migrations 帮助你 93 | 94 | - 自动生成对应的表结构; 95 | - 记录每一次变更,内置的回滚方案; 96 | - “声明式”,可以被重复执行,结果幂等(这意味着解决了多环境的问题); 97 | 98 | ---?color=#ffcfdf 99 | 100 | ## Table of Contents 101 | 102 | - Django Migraiton 的功能 103 | - 👉 工作原理 104 | - 用法和常见问题 105 | - Django 的选择 106 | 107 | +++ 108 | 109 | ### Django migrations 是如何工作的? 110 | 111 | 1. 自动生成每一次的数据库变更; 112 | 2. 执行数据库变更; 113 | 114 | +++ 115 | 116 | ### 重复执行没有副作用 117 | 118 | ``` 119 | $ python manage.py makemigrations 120 | No changes detected 121 | ``` 122 | 123 | +++?color=#ffffff 124 | 125 | ## 生成变更 126 | 127 | ``` 128 | $ python manage.py makemigrations 129 | ``` 130 | 131 | ![](./assets/django-migrations-ppt/django-orm.png) 132 | 133 | +++ 134 | 135 | ### 步骤 136 | 137 | 1. 应用历史的变更,得到上次最新的结构 A; 138 | 2. 对比当前的 Model B; 139 | 3. 生成从 A 状态迁移到 B 状态的变更; 140 | 141 | +++ 142 | 143 | - 这步不需要连接数据库; 144 | - O(n) 操作,如果历史的 migrations 很多了,耗时会很长; 145 | - 不能有歧义; 146 | 147 | +++ 148 | 149 | 把这个过程想象成上帝在看着你下棋。 150 | 151 | 但是他不会记住每一步, 152 | 153 | 每当你 makemigrations 的时候, 154 | 155 | 他会记下来怎么把上次的棋盘变成现在的样子。 156 | 157 | ![](./assets/django-migrations-ppt/chess.png) 158 | 159 | +++ 160 | 161 | ### 歧义 162 | 163 | ![](./assets/django-migrations-ppt/fields.png) 164 | 165 | +++ 166 | 167 | a -> b ? 168 | 169 | a -> c ? 170 | 171 | ``` 172 | Did you rename person.inta to person.intc (a IntegerField)? [y/N] N 173 | Did you rename person.intb to person.intc (a IntegerField)? [y/N] y 174 | Did you rename person.inta to person.intd (a IntegerField)? [y/N] N 175 | ``` 176 | 177 | +++ 178 | 179 | ## 执行变更 180 | 181 | ``` 182 | $ python manage.py migrate 183 | ``` 184 | 185 | +++ 186 | 187 | ### 重复执行不会带来副作用 188 | 189 | ``` 190 | $ python manage.py migrate 191 | Running migrations: 192 | No migrations to apply. 193 | ``` 194 | 195 | +++ 196 | 197 | ### migrate 做了什么? 198 | 199 | - 真正引起数据库结构改变的操作; 200 | - 将 migration files 翻译成 SQL 并执行; 201 | - 记录 migrate 执行到哪一次了; 202 | 203 | ```Python 204 | File: app1/migrations/0006_auto_20190912_1150.py 205 | # Generated by Django 2.2.5 on 2019-09-12 11:50 206 | 207 | from django.db import migrations, models 208 | 209 | 210 | class Migration(migrations.Migration): 211 | 212 | dependencies = [ 213 | ('app1', '0005_auto_20190912_1149'), 214 | ] 215 | 216 | operations = [ 217 | migrations.RenameField( 218 | model_name='person', 219 | old_name='intb', 220 | new_name='intc', 221 | ), 222 | migrations.RemoveField( 223 | model_name='person', 224 | name='inta', 225 | ), 226 | migrations.AddField( 227 | model_name='person', 228 | name='intd', 229 | field=models.IntegerField(default=None), 230 | preserve_default=False, 231 | ), 232 | ] 233 | ``` 234 | 235 | +++ 236 | 237 | ### 如何防止 SQL 重复执行? 238 | 239 | ```SQL 240 | mysql root@localhost:django_example> SELECT * FROM django_migrations LIMIT 3; 241 | +----+--------------+--------------+----------------------------+ 242 | | id | app | name | applied | 243 | +----+--------------+--------------+----------------------------+ 244 | | 1 | contenttypes | 0001_initial | 2019-09-08 09:08:51.254479 | 245 | | 2 | auth | 0001_initial | 2019-09-08 09:08:51.355292 | 246 | | 3 | admin | 0001_initial | 2019-09-08 09:08:51.544356 | 247 | +----+--------------+--------------+----------------------------+ 248 | ``` 249 | 250 | +++ 251 | 252 | ### unapply 功能 253 | 254 | ```Python 255 | ➜ python manage.py migrate app1 0001 256 | Operations to perform: 257 | Target specific migration: 0001_initial, from app1 258 | Running migrations: 259 | Rendering model states... DONE 260 | Unapplying app1.0003_remove_person_intc... OK 261 | Unapplying app1.0002_remove_person_intn... OK 262 | ➜ python manage.py migrate app1 0002 263 | Operations to perform: 264 | Target specific migration: 0002_remove_person_intn, from app1 265 | Running migrations: 266 | Applying app1.0002_remove_person_intn... OK 267 | ``` 268 | 269 | +++ 270 | 271 | ### 总结 272 | 273 | 1. Django 用之前的 migrations 文件作为上一个状态的 source of truth,和当前 274 | 状态作对比,得到 diff 生成新的 migrations; 275 | 2. Django 在数据库中用一张表记录哪些 migrations 被执行过了,防止重复执行; 276 | 277 | ---?color=#ffcfdf 278 | 279 | ## Table of Contents 280 | 281 | - Django Migraiton 的功能 282 | - 工作原理 283 | - 👉 用法和常见问题 284 | - Django 的选择 285 | 286 | +++ 287 | 288 | ## FAQ1 289 | 290 | > 将 migrate 加入到启动命令中? 291 | 292 | 既然 migrate 是幂等的,我把这个操作放到 Docker 的 CMD 293 | 中,每次启动之前自动应用新的变更(如果有). 294 | 295 | What could go wrong? 296 | 297 | +++ 298 | 299 | # DON'T DO THAT! 300 | 301 | +++ 302 | 303 | # Why? 304 | 305 | 1. 多台机器同时启动可能损坏表结构; 306 | 2. 默认当前的数据结构只会被当前的代码使用; 307 | (灰度?回滚?都不可行了) 308 | 309 | +++ 310 | 311 | ## 最佳实践 312 | 313 | 1. 用一个单独的 Docker image 来做 migrate; 314 | 2. migrate 要向后兼容(这是另一个故事了) 315 | 316 | +++ 317 | 318 | ## FAQ2 319 | 320 | > migrate 执行失败 321 | 322 | 为什么会发生? 323 | 324 | 因为 `makemigrations` 的时候并没有连接数据库,是基于逻辑生成的。真正执行的时候可能失败。 325 | 326 | +++ 327 | 328 | ### 如何恢复? 329 | 330 | 1. 复原已经执行过的操作; 331 | 2. 删除 `django_migrations` 记录; 332 | 3. 修复 migrations 文件; 333 | 4. 重新执行。 334 | 335 | +++ 336 | 337 | ## FAQ3 338 | 339 | > 为什么我没有改动任何 Models,但是每次 `makemigrations` 都会生成新的? 340 | 341 | +++ 342 | 343 | ### 还记得 migrations 是怎么生成的吗? 344 | 345 | ![](./assets/django-migrations-ppt/django-orm.png) 346 | 347 | +++ 348 | 349 | Model 和 Migrations 结果对比每次都有 diff: 350 | 351 | - 代码中是不是用了结果不确定的数据结构?(比如 Python3.5 之前的 dict) 352 | - 代码中是否有代码每次执行的结果是不确定的? 353 | 354 | +++ 355 | 356 | ## FAQ4 357 | 358 | > 我和同事在不同的分支生成了 000N 相同的 migrations 怎么办? 359 | 360 | +++ 361 | 362 | ```python 363 | $ python manage.py makemigrations --merge 364 | ``` 365 | 366 | +++ 367 | 368 | ## FAQ5 369 | 370 | > migrations 执行太慢了 371 | 372 | +++ 373 | 374 | ### 重建 migrations 375 | 376 | > 因为 migrations 记录了所有的变更,如果这些变更在一段时间之后对我们不再重要,我们可以把当前的Model状态作为 0001 。 377 | 378 | +++ 379 | 380 | ### 步骤 381 | 382 | 1. 备份数据库和整个项目; 383 | 2. `ls */migrations/*.py | grep -v __init__ | xargs rm` 删除历史 migrations 384 | 3. 重新 `makemigrations` 385 | 4. 删除 `django_migrations` 记录; 386 | 5. `python manage.py migrate --fake` 填充 `django_migrations` 数据; 387 | 388 | +++ 389 | 390 | ## FAQ6 391 | 392 | > 是否应该将生成的 migrations 文件追踪到 git 中? 393 | 394 | 是的。 395 | 396 | +++ 397 | 398 | ### 提交之前进行检查 399 | 400 | ![](./assets/django-migrations-ppt/check-before-commit.png) 401 | 402 | +++ 403 | 404 | ### 💡 尽量让 migrations 有意义,像 git commit message 那样。 405 | 406 | +++ 407 | 408 | ## FAQ7 409 | 410 | > 不要害怕自己写 migrations 411 | 412 | +++ 413 | 414 | 当你想: 415 | 416 | 1. 你的 Model 变化太复杂,想要自己写 Migrations 的时候; 417 | 2. 在 migrate 结构的同时,有一些数据需要变化的时候; 418 | 419 | +++ 420 | 421 | 用 `python manage.py makemigrations --empty` 生成一个空的, 422 | 然后自己写。 423 | 424 | +++ 425 | 426 | RunPython & RunSQL are you friends! 427 | 428 | ```Python 429 | 430 | def my_editor(app, schema_editor): 431 | Person = app.get_model("app1", "Person") 432 | schema_editor.remove_field(Person, "age") 433 | 434 | 435 | class Migration(migrations.Migration): 436 | 437 | dependencies = [("app1", "0001_initial")] 438 | 439 | operations = [migrations.RunPython(my_editor)] 440 | ``` 441 | 442 | +++ 443 | 444 | ### 几点要注意的事情 445 | 446 | 1. `app.get_model`; 447 | 2. 不要在 RunSQL 里面记录表结构变更; 448 | 449 | ---?color=#ffcfdf 450 | 451 | ## Table of Contents 452 | 453 | - Django Migraiton 的功能 454 | - 工作原理 455 | - 用法和常见问题 456 | - 👉 Django 的选择 457 | 458 | +++ 459 | 460 | Django 的 migration 设计: 461 | 462 | 1. migrations 记录每一次变更; 463 | 2. 数据库一张meta表记录变更的执行情况; 464 | 465 | +++ 466 | 467 | ## Before Django Migrations 468 | 469 | ### Django-South 470 | 471 | +++ 472 | 473 | - Django1.6 只支持创建表, 修改Model只能自己去修改表; 474 | - Migrations 方案混杂,几年之后,South 成为事实标准; 475 | - South 遇到了自己的瓶颈, Andrew 希望将migrations加入到 Django core 中,完全替代South; 476 | - 第一个 [Pull Request](https://github.com/django/django/pull/376) 漫长而复杂(~7k changes),这项工作将漫长而困难; 477 | - Andrew 发起[众筹](https://www.kickstarter.com/projects/andrewgodwin/schema-migrations-for-django),希望每周投入80小时到这项工作中; 478 | - 众筹发起1小时完成了目标,4小时筹集 $7000; 479 | - Django1.7 开始支持migrations 🎉 480 | 481 | +++ 482 | 483 | ### Django-South 484 | 485 | ```Python 486 | class Migration(SchemaMigration): 487 | 488 | def forwards(self, orm): 489 | # Adding field 'Knight.name' 490 | db.add_column('app1_knight', 'name', 491 | self.gf('django.db.models.fields.CharField')(default='', max_length=100), 492 | keep_default=False) 493 | 494 | 495 | def backwards(self, orm): 496 | # Deleting field 'Knight.name' 497 | db.delete_column('app1_knight', 'name') 498 | 499 | 500 | models = { 501 | 'app1.knight': { 502 | 'Meta': {'object_name': 'Knight'}, 503 | 'dances_whenever_able': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 504 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 505 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 506 | 'of_the_round_table': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) 507 | } 508 | } 509 | 510 | complete_apps = ['app1'] 511 | ``` 512 | 513 | +++ 514 | 515 | ### Django Migrations vs Django-South 516 | 517 | - Django migrations 没有写 backwards 518 | - Django migrations 没有 models,需要从第一个migrations开始依赖,South 只依赖前一个 519 | 520 | Django migrations 对开发者更友好,South 冗余信息更多,速度更快 521 | 522 | +++ 523 | 524 | Django 这样的设计给我们什么? 525 | 526 | 1. Model 的 source of truth; 527 | 2. 变更历史记录; 528 | 3. 但是没有隐藏所有的事情,migrations 可以被人为干涉; 529 | 4. 方便人工编辑; 530 | 531 | +++ 532 | 533 | ## 我们来看一下其他的方案 534 | 535 | +++ 536 | 537 | ### 没有 migrations 538 | 539 | - 直接对比代码中的 Model 和数据库的 Table 540 | - 消除 diff 541 | 542 | ### 缺点 543 | 544 | - 迁移过程不透明,不可控制 545 | 546 | +++ 547 | 548 | ## 有 migrate 表但是不记录 migrations 549 | 550 | +++ 551 | 552 | ## automigrate 553 | 554 | https://github.com/abe-winter/automigrate 555 | 556 | +++ 557 | 558 | ## 一些反对 ORM 的声音 559 | 560 | - ORM 引入了新的理解成本,大家已经都熟悉SQL了; 561 | - ORM 并没有屏蔽所有的问题,会遇到只能写SQL才能解决; 562 | - 增加了解决问题的复杂性,和了解事实的复杂性; 563 | 564 | +++ 565 | 566 | > Exactly this, I tend to write plain SQL nowadays since you eventually have to work around some ORM specific problems in the end. 567 | 568 | https://lobste.rs/s/ihqxej/orms_are_backwards#c_0x76xn 569 | 570 | 571 | ---?color=#fefdca 572 | 573 | # Thanks! 574 | 575 | - Blog: https://www.kawabangga.com/ 576 | - Slide 地址: https://github.com/laixintao/django-migrations-under-the-hood 577 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | My slide for PyCon China 2019. 2 | 3 | Any comments are welcome! 4 | 5 | Online Preview [gitpitch](https://gitpitch.com/laixintao/django-migrations-under-the-hood) 6 | 7 | 8 | 9 | [PyCon China 2019](https://cn.pycon.org/) 10 | 11 | [Buy tickets](https://www.bagevent.com/event/5293611) 12 | -------------------------------------------------------------------------------- /assets/Untitled.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laixintao/django-migrations-under-the-hood/f84ab235ec1d0e1733f7b2dc23cbccc909aa428a/assets/Untitled.graffle -------------------------------------------------------------------------------- /assets/auto-complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laixintao/django-migrations-under-the-hood/f84ab235ec1d0e1733f7b2dc23cbccc909aa428a/assets/auto-complete.png -------------------------------------------------------------------------------- /assets/chess1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laixintao/django-migrations-under-the-hood/f84ab235ec1d0e1733f7b2dc23cbccc909aa428a/assets/chess1.png -------------------------------------------------------------------------------- /assets/chess2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laixintao/django-migrations-under-the-hood/f84ab235ec1d0e1733f7b2dc23cbccc909aa428a/assets/chess2.png -------------------------------------------------------------------------------- /assets/django-migrations-ppt/basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laixintao/django-migrations-under-the-hood/f84ab235ec1d0e1733f7b2dc23cbccc909aa428a/assets/django-migrations-ppt/basic.png -------------------------------------------------------------------------------- /assets/django-migrations-ppt/check-before-commit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laixintao/django-migrations-under-the-hood/f84ab235ec1d0e1733f7b2dc23cbccc909aa428a/assets/django-migrations-ppt/check-before-commit.png -------------------------------------------------------------------------------- /assets/django-migrations-ppt/chess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laixintao/django-migrations-under-the-hood/f84ab235ec1d0e1733f7b2dc23cbccc909aa428a/assets/django-migrations-ppt/chess.png -------------------------------------------------------------------------------- /assets/django-migrations-ppt/django-migrations-usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laixintao/django-migrations-under-the-hood/f84ab235ec1d0e1733f7b2dc23cbccc909aa428a/assets/django-migrations-ppt/django-migrations-usage.png -------------------------------------------------------------------------------- /assets/django-migrations-ppt/django-orm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laixintao/django-migrations-under-the-hood/f84ab235ec1d0e1733f7b2dc23cbccc909aa428a/assets/django-migrations-ppt/django-orm.png -------------------------------------------------------------------------------- /assets/django-migrations-ppt/fields.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laixintao/django-migrations-under-the-hood/f84ab235ec1d0e1733f7b2dc23cbccc909aa428a/assets/django-migrations-ppt/fields.png -------------------------------------------------------------------------------- /django-migrations-ppt.graffle/data.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laixintao/django-migrations-under-the-hood/f84ab235ec1d0e1733f7b2dc23cbccc909aa428a/django-migrations-ppt.graffle/data.plist -------------------------------------------------------------------------------- /django-migrations-ppt.graffle/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laixintao/django-migrations-under-the-hood/f84ab235ec1d0e1733f7b2dc23cbccc909aa428a/django-migrations-ppt.graffle/image1.png -------------------------------------------------------------------------------- /django-migrations-ppt.graffle/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laixintao/django-migrations-under-the-hood/f84ab235ec1d0e1733f7b2dc23cbccc909aa428a/django-migrations-ppt.graffle/image2.png -------------------------------------------------------------------------------- /drafts.md: -------------------------------------------------------------------------------- 1 | ## Color Scheme 2 | 3 | https://colorhunt.co/palette/39602 4 | 5 | - ToC: ffcfdf 6 | - Content: fefdca 7 | 8 | 9 | 10 | --- 11 | 12 | - makemigrations 是不需要连接数据库的; 13 | - 14 | -------------------------------------------------------------------------------- /example/app1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laixintao/django-migrations-under-the-hood/f84ab235ec1d0e1733f7b2dc23cbccc909aa428a/example/app1/__init__.py -------------------------------------------------------------------------------- /example/app1/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /example/app1/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class App1Config(AppConfig): 5 | name = 'app1' 6 | -------------------------------------------------------------------------------- /example/app1/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.5 on 2019-09-15 04:59 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Person', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('name', models.CharField(max_length=64)), 19 | ('age', models.IntegerField()), 20 | ('intc', models.IntegerField()), 21 | ('intn', models.IntegerField()), 22 | ], 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /example/app1/migrations/0002_remove_person_intn.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.5 on 2019-09-15 05:02 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('app1', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='person', 15 | name='intn', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /example/app1/migrations/0003_remove_person_intc.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.5 on 2019-09-15 05:02 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('app1', '0002_remove_person_intn'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='person', 15 | name='intc', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /example/app1/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laixintao/django-migrations-under-the-hood/f84ab235ec1d0e1733f7b2dc23cbccc909aa428a/example/app1/migrations/__init__.py -------------------------------------------------------------------------------- /example/app1/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Person(models.Model): 5 | name = models.CharField(max_length=64) 6 | age = models.IntegerField() 7 | -------------------------------------------------------------------------------- /example/app1/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /example/app1/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /example/example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laixintao/django-migrations-under-the-hood/f84ab235ec1d0e1733f7b2dc23cbccc909aa428a/example/example/__init__.py -------------------------------------------------------------------------------- /example/example/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for example project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.2.5. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.2/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'i9hr%m-)s%rz3pr_%(7z+hiyvihr=gi63@%k2b*@iog^_^9*vx' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'app1', 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | 'django.middleware.security.SecurityMiddleware', 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | ] 52 | 53 | ROOT_URLCONF = 'example.urls' 54 | 55 | TEMPLATES = [ 56 | { 57 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 58 | 'DIRS': [], 59 | 'APP_DIRS': True, 60 | 'OPTIONS': { 61 | 'context_processors': [ 62 | 'django.template.context_processors.debug', 63 | 'django.template.context_processors.request', 64 | 'django.contrib.auth.context_processors.auth', 65 | 'django.contrib.messages.context_processors.messages', 66 | ], 67 | }, 68 | }, 69 | ] 70 | 71 | WSGI_APPLICATION = 'example.wsgi.application' 72 | 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/2.2/ref/settings/#databases 76 | 77 | DATABASES = { 78 | 'default': { 79 | 'ENGINE': 'django.db.backends.mysql', 80 | 'NAME': 'django_example', 81 | 'USER': 'root', 82 | } 83 | } 84 | 85 | 86 | # Password validation 87 | # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators 88 | 89 | AUTH_PASSWORD_VALIDATORS = [ 90 | { 91 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 92 | }, 93 | { 94 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 95 | }, 96 | { 97 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 98 | }, 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 101 | }, 102 | ] 103 | 104 | 105 | # Internationalization 106 | # https://docs.djangoproject.com/en/2.2/topics/i18n/ 107 | 108 | LANGUAGE_CODE = 'en-us' 109 | 110 | TIME_ZONE = 'UTC' 111 | 112 | USE_I18N = True 113 | 114 | USE_L10N = True 115 | 116 | USE_TZ = True 117 | 118 | 119 | # Static files (CSS, JavaScript, Images) 120 | # https://docs.djangoproject.com/en/2.2/howto/static-files/ 121 | 122 | STATIC_URL = '/static/' 123 | -------------------------------------------------------------------------------- /example/example/urls.py: -------------------------------------------------------------------------------- 1 | """example URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.2/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path 18 | 19 | urlpatterns = [ 20 | path('admin/', admin.site.urls), 21 | ] 22 | -------------------------------------------------------------------------------- /example/example/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for example project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /example/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /south_example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laixintao/django-migrations-under-the-hood/f84ab235ec1d0e1733f7b2dc23cbccc909aa428a/south_example/__init__.py -------------------------------------------------------------------------------- /south_example/app1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laixintao/django-migrations-under-the-hood/f84ab235ec1d0e1733f7b2dc23cbccc909aa428a/south_example/app1/__init__.py -------------------------------------------------------------------------------- /south_example/app1/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding model 'Knight' 12 | db.create_table('app1_knight', ( 13 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('name', self.gf('django.db.models.fields.CharField')(max_length=100)), 15 | ('of_the_round_table', self.gf('django.db.models.fields.BooleanField')(default=False)), 16 | )) 17 | db.send_create_signal('app1', ['Knight']) 18 | 19 | 20 | def backwards(self, orm): 21 | # Deleting model 'Knight' 22 | db.delete_table('app1_knight') 23 | 24 | 25 | models = { 26 | 'app1.knight': { 27 | 'Meta': {'object_name': 'Knight'}, 28 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 29 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 30 | 'of_the_round_table': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) 31 | } 32 | } 33 | 34 | complete_apps = ['app1'] -------------------------------------------------------------------------------- /south_example/app1/migrations/0003_auto__add_field_knight_age.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding field 'Knight.age' 12 | db.add_column('app1_knight', 'age', 13 | self.gf('django.db.models.fields.IntegerField')(null=True), 14 | keep_default=False) 15 | 16 | 17 | def backwards(self, orm): 18 | # Deleting field 'Knight.age' 19 | db.delete_column('app1_knight', 'age') 20 | 21 | 22 | models = { 23 | 'app1.knight': { 24 | 'Meta': {'object_name': 'Knight'}, 25 | 'age': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), 26 | 'dances_whenever_able': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 27 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 28 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 29 | 'of_the_round_table': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) 30 | } 31 | } 32 | 33 | complete_apps = ['app1'] -------------------------------------------------------------------------------- /south_example/app1/migrations/0004_auto__add_field_knight_foo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding field 'Knight.foo' 12 | db.add_column('app1_knight', 'foo', 13 | self.gf('django.db.models.fields.IntegerField')(null=True), 14 | keep_default=False) 15 | 16 | 17 | def backwards(self, orm): 18 | # Deleting field 'Knight.foo' 19 | db.delete_column('app1_knight', 'foo') 20 | 21 | 22 | models = { 23 | 'app1.knight': { 24 | 'Meta': {'object_name': 'Knight'}, 25 | 'age': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), 26 | 'dances_whenever_able': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 27 | 'foo': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), 28 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 29 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 30 | 'of_the_round_table': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) 31 | } 32 | } 33 | 34 | complete_apps = ['app1'] -------------------------------------------------------------------------------- /south_example/app1/migrations/0005_auto__del_field_knight_foo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Deleting field 'Knight.foo' 12 | db.delete_column('app1_knight', 'foo') 13 | db.delete_column('app1_knight', 'name') 14 | 15 | 16 | def backwards(self, orm): 17 | # Adding field 'Knight.foo' 18 | db.add_column('app1_knight', 'foo', 19 | self.gf('django.db.models.fields.IntegerField')(null=True), 20 | keep_default=False) 21 | 22 | 23 | models = { 24 | 'app1.knight': { 25 | 'Meta': {'object_name': 'Knight'}, 26 | 'age': ('django.db.models.fields.IntegerField', [], {'null': 'True'}), 27 | 'dances_whenever_able': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 28 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 29 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 30 | 'of_the_round_table': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) 31 | } 32 | } 33 | 34 | complete_apps = ['app1'] 35 | -------------------------------------------------------------------------------- /south_example/app1/migrations/0006_auto__del_field_knight_age.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Deleting field 'Knight.age' 12 | db.delete_column('app1_knight', 'age') 13 | 14 | 15 | def backwards(self, orm): 16 | # Adding field 'Knight.age' 17 | db.add_column('app1_knight', 'age', 18 | self.gf('django.db.models.fields.IntegerField')(null=True), 19 | keep_default=False) 20 | 21 | 22 | models = { 23 | 'app1.knight': { 24 | 'Meta': {'object_name': 'Knight'}, 25 | 'dances_whenever_able': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 26 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 27 | 'of_the_round_table': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) 28 | } 29 | } 30 | 31 | complete_apps = ['app1'] 32 | -------------------------------------------------------------------------------- /south_example/app1/migrations/0007_auto__add_field_knight_name.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding field 'Knight.name' 12 | db.add_column('app1_knight', 'name', 13 | self.gf('django.db.models.fields.CharField')(default='', max_length=100), 14 | keep_default=False) 15 | 16 | 17 | def backwards(self, orm): 18 | # Deleting field 'Knight.name' 19 | db.delete_column('app1_knight', 'name') 20 | 21 | 22 | models = { 23 | 'app1.knight': { 24 | 'Meta': {'object_name': 'Knight'}, 25 | 'dances_whenever_able': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 26 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 27 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 28 | 'of_the_round_table': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) 29 | } 30 | } 31 | 32 | complete_apps = ['app1'] -------------------------------------------------------------------------------- /south_example/app1/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/laixintao/django-migrations-under-the-hood/f84ab235ec1d0e1733f7b2dc23cbccc909aa428a/south_example/app1/migrations/__init__.py -------------------------------------------------------------------------------- /south_example/app1/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | class Knight(models.Model): 4 | name = models.CharField(max_length=100) 5 | of_the_round_table = models.BooleanField() 6 | dances_whenever_able = models.BooleanField() 7 | -------------------------------------------------------------------------------- /south_example/app1/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | 11 | class SimpleTest(TestCase): 12 | def test_basic_addition(self): 13 | """ 14 | Tests that 1 + 1 always equals 2. 15 | """ 16 | self.assertEqual(1 + 1, 2) 17 | -------------------------------------------------------------------------------- /south_example/app1/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | -------------------------------------------------------------------------------- /south_example/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from django.core.management import execute_manager 3 | import imp 4 | try: 5 | imp.find_module('settings') # Assumed to be in the same directory. 6 | except ImportError: 7 | import sys 8 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__) 9 | sys.exit(1) 10 | 11 | import settings 12 | 13 | if __name__ == "__main__": 14 | execute_manager(settings) 15 | -------------------------------------------------------------------------------- /south_example/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for south_example project. 2 | 3 | DEBUG = True 4 | TEMPLATE_DEBUG = DEBUG 5 | 6 | ADMINS = ( 7 | # ('Your Name', 'your_email@example.com'), 8 | ) 9 | 10 | MANAGERS = ADMINS 11 | DATABASES = { 12 | 'default': { 13 | 'ENGINE': 'django.db.backends.mysql', 14 | 'NAME': 'django_south', 15 | 'USER': 'root', 16 | } 17 | } 18 | 19 | # Local time zone for this installation. Choices can be found here: 20 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 21 | # although not all choices may be available on all operating systems. 22 | # On Unix systems, a value of None will cause Django to use the same 23 | # timezone as the operating system. 24 | # If running in a Windows environment this must be set to the same as your 25 | # system time zone. 26 | TIME_ZONE = 'America/Chicago' 27 | 28 | # Language code for this installation. All choices can be found here: 29 | # http://www.i18nguy.com/unicode/language-identifiers.html 30 | LANGUAGE_CODE = 'en-us' 31 | 32 | SITE_ID = 1 33 | 34 | # If you set this to False, Django will make some optimizations so as not 35 | # to load the internationalization machinery. 36 | USE_I18N = True 37 | 38 | # If you set this to False, Django will not format dates, numbers and 39 | # calendars according to the current locale 40 | USE_L10N = True 41 | 42 | # Absolute filesystem path to the directory that will hold user-uploaded files. 43 | # Example: "/home/media/media.lawrence.com/media/" 44 | MEDIA_ROOT = '' 45 | 46 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 47 | # trailing slash. 48 | # Examples: "http://media.lawrence.com/media/", "http://example.com/media/" 49 | MEDIA_URL = '' 50 | 51 | # Absolute path to the directory static files should be collected to. 52 | # Don't put anything in this directory yourself; store your static files 53 | # in apps' "static/" subdirectories and in STATICFILES_DIRS. 54 | # Example: "/home/media/media.lawrence.com/static/" 55 | STATIC_ROOT = '' 56 | 57 | # URL prefix for static files. 58 | # Example: "http://media.lawrence.com/static/" 59 | STATIC_URL = '/static/' 60 | 61 | # URL prefix for admin static files -- CSS, JavaScript and images. 62 | # Make sure to use a trailing slash. 63 | # Examples: "http://foo.com/static/admin/", "/static/admin/". 64 | ADMIN_MEDIA_PREFIX = '/static/admin/' 65 | 66 | # Additional locations of static files 67 | STATICFILES_DIRS = ( 68 | # Put strings here, like "/home/html/static" or "C:/www/django/static". 69 | # Always use forward slashes, even on Windows. 70 | # Don't forget to use absolute paths, not relative paths. 71 | ) 72 | 73 | # List of finder classes that know how to find static files in 74 | # various locations. 75 | STATICFILES_FINDERS = ( 76 | 'django.contrib.staticfiles.finders.FileSystemFinder', 77 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 78 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder', 79 | ) 80 | 81 | # Make this unique, and don't share it with anybody. 82 | SECRET_KEY = '8l$x(fbycvj)=5jku^!s5edtrs9nf%6)e**4n0id2xn80qngk0' 83 | 84 | # List of callables that know how to import templates from various sources. 85 | TEMPLATE_LOADERS = ( 86 | 'django.template.loaders.filesystem.Loader', 87 | 'django.template.loaders.app_directories.Loader', 88 | # 'django.template.loaders.eggs.Loader', 89 | ) 90 | 91 | MIDDLEWARE_CLASSES = ( 92 | 'django.middleware.common.CommonMiddleware', 93 | 'django.contrib.sessions.middleware.SessionMiddleware', 94 | 'django.middleware.csrf.CsrfViewMiddleware', 95 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 96 | 'django.contrib.messages.middleware.MessageMiddleware', 97 | ) 98 | 99 | ROOT_URLCONF = 'south_example.urls' 100 | 101 | TEMPLATE_DIRS = ( 102 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 103 | # Always use forward slashes, even on Windows. 104 | # Don't forget to use absolute paths, not relative paths. 105 | ) 106 | 107 | INSTALLED_APPS = ( 108 | 'django.contrib.auth', 109 | 'django.contrib.contenttypes', 110 | 'django.contrib.sessions', 111 | 'django.contrib.sites', 112 | 'django.contrib.messages', 113 | 'django.contrib.staticfiles', 114 | # Uncomment the next line to enable the admin: 115 | # 'django.contrib.admin', 116 | # Uncomment the next line to enable admin documentation: 117 | # 'django.contrib.admindocs', 118 | 'app1', 119 | 'south', 120 | ) 121 | 122 | # A sample logging configuration. The only tangible logging 123 | # performed by this configuration is to send an email to 124 | # the site admins on every HTTP 500 error. 125 | # See http://docs.djangoproject.com/en/dev/topics/logging for 126 | # more details on how to customize your logging configuration. 127 | LOGGING = { 128 | 'version': 1, 129 | 'disable_existing_loggers': False, 130 | 'handlers': { 131 | 'mail_admins': { 132 | 'level': 'ERROR', 133 | 'class': 'django.utils.log.AdminEmailHandler' 134 | } 135 | }, 136 | 'loggers': { 137 | 'django.request': { 138 | 'handlers': ['mail_admins'], 139 | 'level': 'ERROR', 140 | 'propagate': True, 141 | }, 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /south_example/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import patterns, include, url 2 | 3 | # Uncomment the next two lines to enable the admin: 4 | # from django.contrib import admin 5 | # admin.autodiscover() 6 | 7 | urlpatterns = patterns('', 8 | # Examples: 9 | # url(r'^$', 'south_example.views.home', name='home'), 10 | # url(r'^south_example/', include('south_example.foo.urls')), 11 | 12 | # Uncomment the admin/doc line below to enable admin documentation: 13 | # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), 14 | 15 | # Uncomment the next line to enable the admin: 16 | # url(r'^admin/', include(admin.site.urls)), 17 | ) 18 | --------------------------------------------------------------------------------