Split Instructor model to be OneToOne with new Person model¶
Just as a thought experiment, let's decide that we want a generic Person model. For the time being, this model will be one-to-one with an Instructor. Let's assume that our production database has live data in it that we don't want to break things, so we'll need some migration magic.
We want to:
1. Create the new Model, Serializer, and Views.
2. Make the new migration and customize it
to fix our data, moving Instructor.name
to Person.name
and
add a relationship from Instructor
to that Person
.
3. Update our test fixtures to reflect the changes, including
migrating forward and backward.
4. Finally, be fairly confident that this will run on our production database.
New Person models, serializers, views¶
As we did previously when we added Instructor, let's create the new stuff and check it all in as one git commit. We'll see why in a second.
You may note that our makedocs
script generates a UML diagram into docs/media/current-uml.png
.
Let's rename that to reflect our current person model:
1 |
|
Here's the new model:
Custom Person migration¶
We customize the migration:
1. Rename it from the auto name.
1. Move adding the new Instructor.person
OneToOneField up to the top.
1. Add Python code to move the Instructor.name
field into Person.name
and vice-versa.
1 2 3 4 5 6 7 |
|
1 |
|
Here's the edited version of the person migration. Note the instr_name_to_person()
and
person_name_to_instr()
forward and reverse migrations.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
|
Now we'll commit this work:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Update fixtures¶
Our test fixtures currently only work with an older version of our app's models, so let's: 1. Checkout that older version. 2. Start with a fresh sqlite3 database. 3. Migrate and load the current fixture data. 4. Dump out the new fixture data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
|
Run tox to make sure our test cases still work.
1 |
|
Make sure reverse migrations work¶
Let's also test the reverse migration to make sure we got that right. This works fine if there's no data loaded, but fails if there is data to migrate:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
|
It's clear what the problem is (with database backend debugging turned on).
instructor.name
can't be NULL but the steps used to reverse the migration do in fact make it NULL:
1 |
|
It appears that this is a
documented restricition of RemoveField
however, here's a simple workaround: Simply convert the instructor.name
field to NULLable with a default value
as the first step in the migration. This will then reverse back to allow the preceding INSERT to work and then
further reverse to make the name non-NULLable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
|