{"id":4917,"date":"2017-06-27T20:46:43","date_gmt":"2017-06-27T20:46:43","guid":{"rendered":"https:\/\/cheesecakelabs.com\/blog\/?p=4917\/"},"modified":"2026-08-15T07:56:27","modified_gmt":"2026-08-15T07:56:27","slug":"really-annoys-django-migrations","status":"publish","type":"post","link":"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/","title":{"rendered":"What really annoys me about Django migrations"},"content":{"rendered":"<p><span style=\"font-weight: 400\">Automated database migrations have been a convenient way of dealing with schema changes for a long time in Django. It&#8217;s been only 3 years since <a href=\"https:\/\/www.djangoproject.com\/weblog\/2014\/sep\/02\/release-17-final\/\">migrations have been incorporated into Django<\/a> but <a href=\"https:\/\/south.readthedocs.io\/en\/latest\/\">South<\/a> had been the de-facto solution since 2008.<\/span><\/p>\n<p><span style=\"font-weight: 400\">The same way an ORM allows us to forget about SQL when writing queries to the database, migrations make sure we don&#8217;t write a single &#8216;ALTER TABLE&#8217; in<\/span> <span style=\"font-weight: 400\">our schema changes. Some may argue that&#8217;s bad: we &#8220;lose control&#8221; over a critical part of our infrastructure, we don&#8217;t know how to write SQL anymore when needed, we&#8217;re not sure how that operation is really translated into SQL, etc, etc. Ok, these points are actually valid. However, the <a href=\"https:\/\/cheesecakelabs.com\/blog\/blog\/django-framework-app-development\/\">Django<\/a> migrations module is more than just a way of automatically generating and applying SQL statements, it&#8217;s also a transparent API to write your own database changes in <a href=\"https:\/\/cheesecakelabs.com\/blog\/blog\/biggest-benefits-of-python\/\">Python<\/a>. It comes with wheels for those who need it (or trust enough) and tools for those who like to get their hands dirty.<\/span><\/p>\n<p><!--more--><\/p>\n<p><span style=\"font-weight: 400\">At this point, you must have noticed already on which team I play (it&#8217;s the pro-automated-migration one, if it was not clear). Still I have a critical point of view when it comes to it. It&#8217;s no secret that working with migrations on medium-to-large-sized teams can be quite annoying, having to resolve migration conflicts on each deploy sometimes. And this is old news, I remember even the South library had a dedicated section: <\/span><a href=\"https:\/\/south.readthedocs.io\/en\/latest\/tutorial\/part5.html\"><b>&#8220;Part 5: Teams and Workflow&#8221;<\/b><\/a><span style=\"font-weight: 400\">, explaining best practices to avoid and resolve conflicts.<\/span><\/p>\n<p><span style=\"font-weight: 400\"> In these almost 10 years of Django migrations, there&#8217;s plenty of literature on the Internet with solutions to this topic, which summarizes to: <\/span><a href=\"https:\/\/www.algotech.solutions\/blog\/python\/django-migrations-and-how-to-manage-conflicts\/\"><span style=\"font-weight: 400\">avoid working on the same app, &#8211;merge them, manually change the migration dependencies or rollback and re-apply migrations<\/span><\/a><span style=\"font-weight: 400\">. Actually, there are also data migrations and database locks and downtime, but I won&#8217;t go over them, because this is not what annoys me the most.<\/span><\/p>\n<h2><span style=\"font-weight: 400\">Why does Django raise a ProgrammingError after switching branches?<\/span><\/h2>\n<p><span style=\"font-weight: 400\">This is what annoys me the most about Django migrations: you apply a migration on one branch, switch to another branch where that migration file does not exist, and the database schema is now ahead of your code. Django raises a ProgrammingError as soon as you touch the affected model.<\/span><\/p>\n<p><img decoding=\"async\" class=\"alignnone wp-image-4920\" src=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2017\/06\/Screen-Shot-2017-06-22-at-13.55.40.png\" alt=\"ProgrammingError Migration\" width=\"818\" height=\"275\" srcset=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2017\/06\/Screen-Shot-2017-06-22-at-13.55.40.png 1180w, https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2017\/06\/Screen-Shot-2017-06-22-at-13.55.40-768x258.png 768w\" sizes=\"(max-width: 818px) 100vw, 818px\" \/><\/p>\n<p><span style=\"font-weight: 400\">Let me try to put it in words:<\/span><\/p>\n<ol>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">I was in branch <\/span><b>bernardo\/remove-favorite-color<\/b><span style=\"font-weight: 400\">.<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Removed the field <\/span><b>&#8220;favorite_color&#8221;<\/b><span style=\"font-weight: 400\"> from my <\/span><b>Profile<\/b><span style=\"font-weight: 400\"> model on <\/span><b>core<\/b><span style=\"font-weight: 400\"> app.<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Generated the migration:<br \/>\n<\/span>&nbsp;<code class=\"language-bash\" style=\"font-size: 0.75em\">python manage.py makemigrations core<\/code><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Applied it:<br \/>\n<code class=\"language-bash\" style=\"font-size: 0.75em\">python manage.py migrate core<\/code><\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Committed:<br \/>\n<code class=\"language-bash\" style=\"font-size: 0.75em\">git add . &amp;&amp; git commit -m \"Remove favorite color from profile\"<\/code><\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">And switched to branch <\/span><b>bernardo\/some-other-feature<\/b><span style=\"font-weight: 400\">.<\/span><\/li>\n<\/ol>\n<p><span style=\"font-weight: 400\">Then you start making some code changes on another app, nothing related to your Profile model, spend a couple of minutes doing it, open Django admin to human-test it and remember you have to create another profile. Nothing wrong with that, right? Nope, you get an error.<\/span><\/p>\n<p><span style=\"font-weight: 400\">Some may say I&#8217;m overreacting, because it&#8217;s a simple change to fix it:<\/span><\/p>\n<ol>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Stash your current changes:<br \/>\n<code class=\"language-bash\" style=\"font-size: 0.75em\">git add . &amp;&amp; git stash<\/code><\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Go back to <\/span><b>bernardo\/remove-favorite-color<\/b><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Get the migration name:<br \/>\n<code class=\"language-bash\" style=\"font-size: 0.75em\">python manage.py showmigrations core<\/code><\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Rollback the migration:<br \/>\n<code class=\"language-bash\" style=\"font-size: 0.75em\">python manage.py migrate core 0002_auto_20170618_1549<\/code><\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Go back to <\/span><b>bernardo\/some-other-feature<\/b><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Unstash changes:<br \/>\n<code class=\"language-bash\" style=\"font-size: 0.75em\">git stash pop &amp;&amp; git reset<\/code><\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Profit<\/span><\/li>\n<\/ol>\n<p><span style=\"font-weight: 400\">Yet the key point is having to stop all your line of thought, rollback the migrations and get back to it. That&#8217;s not only annoying, it&#8217;s counter-productive. What I wanted is something as simple as:<\/span><\/p>\n<pre><code class=\"language-bash\">$ ~ git checkout bernardo\/some-other-branch\n$ These migrations are applied to the database and are not on your code:\n$ - core\/migrations\/0002_auto_20170618_1549.py\n$ &nbsp;&nbsp;&nbsp;- RemoveField(model_name='profile', name='favorite_color')\n$ Do you want me to resolve this issue? [y\/n]\n<\/code><\/pre>\n<h2><span style=\"font-weight: 400\">One solution<\/span><\/h2>\n<p><span style=\"font-weight: 400\">One day I stumbled upon this guy: <\/span><a href=\"https:\/\/stackoverflow.com\/questions\/32682293\/django-migrations-workflow-with-multiple-dev-branches\"><span style=\"font-weight: 400\">django migrations &#8211; workflow with multiple dev branches<\/span><\/a><span style=\"font-weight: 400\">. It&#8217;s a Stack Overflow question asking whether there&#8217;s a git-hook to deal with these sorts of things. I did find some solutions, but not quite what I was looking for (mainly <\/span><a href=\"https:\/\/gist.github.com\/gurglet\/1780139\"><span style=\"font-weight: 400\">this gist<\/span><\/a><span style=\"font-weight: 400\"> and <\/span><a href=\"https:\/\/github.com\/agiliq\/compass\"><span style=\"font-weight: 400\">this lib<\/span><\/a><span style=\"font-weight: 400\">). Talking to other devs here at Cheesecake Labs, we thought this deserved some attention. My first try was actually to do something similar to the question&#8217;s accepted answer suggestion:<\/span><\/p>\n<blockquote><p>1. Just before switching [branch], you dump the list of currently applied migrations into a temporary file mybranch_database_state.txt<br \/>\n2. Then, you apply myfeature branch migrations, if any<br \/>\n3. Then, when checking back mybranch, you reapply your previous database state by looking to the dump file.<\/p><\/blockquote>\n<p><span style=\"font-weight: 400\">Git has only a <\/span><b>post-checkout <\/b><span style=\"font-weight: 400\">hook, so no good to the first item on the list. It&#8217;s not possible to know the operations of applied migrations after the checkout happened. Django stores applied migrations on a database table called <\/span><b>django_migrations<\/b><span style=\"font-weight: 400\">, but all we have there is a migration file name and app, without the migration operations it had applied. We wanted the hook to be a bit smarter: some migration operations don&#8217;t raise ProgrammingErrors, so we wanted to provide this information to the developer and avoid unnecessary rollbacks.<\/span><\/p>\n<h2><span style=\"font-weight: 400\">Our solution<\/span><\/h2>\n<p><span style=\"font-weight: 400\">To have the desired output mentioned above, we started off with this set of short-term goals:<\/span><\/p>\n<ol>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Find out whether there are applied migrations on the previous branch, that are not present on the target branch.<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Read the contents of these migrations and find all operations.<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Warn the user, when the checkout is done, that a set of migrations are applied and not tracked.<\/span><\/li>\n<\/ol>\n<p><span style=\"font-weight: 400\">After <\/span><b>git checkout<\/b><span style=\"font-weight: 400\"> finishes, it calls a <\/span><b>post-checkout<\/b><span style=\"font-weight: 400\"> hook passing as argument the two branch references and whether it was a branch checkout: <\/span><\/p>\n<pre><code class=\"language-bash\">$ post-checkout &lt;previous-ref&gt; &lt;target-ref&gt; &lt;is-branch-checkout&gt;<\/code><\/pre>\n<p><span style=\"font-weight: 400\">And then our hook has to follow these steps:<\/span><\/p>\n<ol>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Find the closest commit ancestor between the two references passed by the post-checkout hook, which translates basically to: find where in the history the two branches have diverged. We use <\/span><b>git-merge-base<\/b><span style=\"font-weight: 400\"> command for that.<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">With a <\/span><b>git-diff<\/b><span style=\"font-weight: 400\"> command, list all files that have been changed between the previous branch and the ancestor commit.<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">From this list, filter all files that are migration modules. By default, Django creates a <\/span><b>migration<\/b><span style=\"font-weight: 400\"> module on each app, but the name can be changed using the <\/span><b>MIGRATION_MODULES <\/b><span style=\"font-weight: 400\">setting, so we take this into consideration.<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">With all migrations in hand, check which of them are <\/span><b>applied to the database<\/b><span style=\"font-weight: 400\">. We do that by querying the database, using Django&#8217;s <\/span><b>MigrationRecorder<\/b><span style=\"font-weight: 400\"> class, which already provides an&nbsp;<strong>applied_migrations<\/strong><\/span><b>&nbsp;<\/b><span style=\"font-weight: 400\">helper function.<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Now with the final migration files list, we can<\/span><span style=\"font-weight: 400\">&nbsp;<strong>execute&nbsp;<\/strong>their code and&nbsp;<\/span><b>discover operations for each migration<\/b><span style=\"font-weight: 400\">.<\/span><\/li>\n<\/ol>\n<p><span style=\"font-weight: 400\">We&#8217;ve created a library called <\/span><b>django-nomad<\/b><span style=\"font-weight: 400\">. So far, we have implemented steps 1 to 5 and a git-hook installer. The code is still in the first stages, but <\/span><a href=\"https:\/\/github.com\/CheesecakeLabs\/django-nomad\"><span style=\"font-weight: 400\">it&#8217;s open on GitHub<\/span><\/a><span style=\"font-weight: 400\"> and accepting suggestions and Pull Requests.<\/span><\/p>\n<h2><span style=\"font-weight: 400\">What&#8217;s next?<\/span><\/h2>\n<p><span style=\"font-weight: 400\">In the long term, we would like to <\/span><b>automatically resolve<\/b><span style=\"font-weight: 400\"> the mentioned issues. But this raises a couple of concerns, since simply rolling back the previous migrations may cause data loss, and despite being only a <a href=\"https:\/\/cheesecakelabs.com\/blog\/blog\/building-app-phase-3-product-development\/\">development<\/a> environment, it can be undesired. <\/span><\/p>\n<p><span style=\"font-weight: 400\">When there&#8217;s a problem, there are also possibilities (quoting my friend Jonatas in <a href=\"https:\/\/cheesecakelabs.com\/blog\/blog\/what-is-serverless-all-about\/\">his post about a Serverless architecture<\/a>):<\/span><\/p>\n<ol>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">We could create a separate schema and override the database connector to switch between them based on the branch, whenever a clash happens.<\/span><\/li>\n<li style=\"font-weight: 400\"><span style=\"font-weight: 400\">Instead of dumping the whole schema, we could maintain only a separate table with different migrations applied, and update the <\/span><b>db_table<\/b><span style=\"font-weight: 400\"> attribute on each Model.<\/span><\/li>\n<\/ol>\n<p><span style=\"font-weight: 400\">These are only in the field of ideas and were not put into thorough consideration to find pros, cons or blockers yet. Anyway they are a good start.<\/span><\/p>\n<p><span style=\"font-weight: 400\">Thoughts? Suggestions? We&#8217;re doing something wrong? Help is always welcome. Thanks!<\/span><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Automated database migrations have been a convenient way of dealing with schema changes for a long time in Django. It&#8217;s been only 3 years since migrations have been incorporated into Django but South had been the de-facto solution since 2008. The same way an ORM allows us to forget about SQL when writing queries to [&hellip;]<\/p>\n","protected":false},"author":65,"featured_media":4951,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_yoast_wpseo_title":"Django Migrations: What Really Annoys Me About Branching","_yoast_wpseo_metadesc":"What makes Django migrations painful once branches diverge, why the generated files collide, and the workarounds worth using on a real project.","_yoast_wpseo_meta-robots-noindex":"","_yoast_wpseo_canonical":"","ai_summary":"The post discusses the productivity issue in Django migrations when switching between git branches, where migrations applied in one branch cause errors in another, requiring manual rollback steps that interrupt developer workflow.\nThe author introduces django-nomad, a library with a git post-checkout hook that detects applied migrations missing from the target branch, reads their operations, and warns the developer about untracked applied migrations.\nThe current implementation covers detection and warning steps, using git-merge-base, git-diff, Django's MigrationRecorder, and respecting MIGRATION_MODULES settings; the code is open source on GitHub and accepting contributions.\nFuture plans include automatic resolution of migration conflicts, with ideas like creating separate schemas per branch or maintaining a separate table tracking branch-specific migrations, though concerns about data loss remain.","ai_summary_en":"The post discusses the productivity issue in Django migrations when switching between git branches, where migrations applied in one branch cause errors in another, requiring manual rollback steps that interrupt developer workflow.\nThe author introduces django-nomad, a library with a git post-checkout hook that detects applied migrations missing from the target branch, reads their operations, and warns the developer about untracked applied migrations.\nThe current implementation covers detection and warning steps, using git-merge-base, git-diff, Django's MigrationRecorder, and respecting MIGRATION_MODULES settings; the code is open source on GitHub and accepting contributions.\nFuture plans include automatic resolution of migration conflicts, with ideas like creating separate schemas per branch or maintaining a separate table tracking branch-specific migrations, though concerns about data loss remain.","ai_summary_pt-br":"","footnotes":"","ckl_wpml_lang":"","ckl_wpml_source_id":0,"ckl_wpml_status":""},"categories":[1425,432],"tags":[],"class_list":["post-4917","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-product-engineering","category-engineering"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.1.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Django Migrations: What Really Annoys Me About Branching<\/title>\n<meta name=\"description\" content=\"What makes Django migrations painful once branches diverge, why the generated files collide, and the workarounds worth using on a real project.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Django Migrations: What Really Annoys Me About Branching\" \/>\n<meta property=\"og:description\" content=\"What makes Django migrations painful once branches diverge, why the generated files collide, and the workarounds worth using on a real project.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/\" \/>\n<meta property=\"og:site_name\" content=\"Cheesecake Labs\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/cheesecakelabs\" \/>\n<meta property=\"article:published_time\" content=\"2017-06-27T20:46:43+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-08-15T07:56:27+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2017\/06\/Banner_database2.png\" \/>\n\t<meta property=\"og:image:width\" content=\"2000\" \/>\n\t<meta property=\"og:image:height\" content=\"720\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Cheesecake Labs\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@cheesecakelabs\" \/>\n<meta name=\"twitter:site\" content=\"@cheesecakelabs\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"7 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/\"},\"author\":{\"name\":\"Bernardo Smaniotto\"},\"headline\":\"What really annoys me about Django migrations\",\"datePublished\":\"2017-06-27T20:46:43+00:00\",\"dateModified\":\"2026-08-15T07:56:27+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/\"},\"wordCount\":1188,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2017\/06\/Banner_database2.png\",\"articleSection\":[\"Product Engineering\",\"Engineering\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/\",\"url\":\"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/\",\"name\":\"Django Migrations: What Really Annoys Me About Branching\",\"isPartOf\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2017\/06\/Banner_database2.png\",\"datePublished\":\"2017-06-27T20:46:43+00:00\",\"dateModified\":\"2026-08-15T07:56:27+00:00\",\"description\":\"What makes Django migrations painful once branches diverge, why the generated files collide, and the workarounds worth using on a real project.\",\"breadcrumb\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/#primaryimage\",\"url\":\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2017\/06\/Banner_database2.png\",\"contentUrl\":\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2017\/06\/Banner_database2.png\",\"width\":2000,\"height\":720,\"caption\":\"Django migrations\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cheesecakelabs.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"What really annoys me about Django migrations\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/#website\",\"url\":\"https:\/\/cheesecakelabs.com\/blog\/\",\"name\":\"Cheesecake Labs\",\"description\":\"AI Implementation, Data and Product Engineering\",\"publisher\":{\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cheesecakelabs.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/#organization\",\"name\":\"Cheesecake Labs\",\"alternateName\":\"Cheesecake Labs Inc\",\"url\":\"https:\/\/cheesecakelabs.com\/\",\"logo\":{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/cheesecakelabs.com\/#logo\",\"url\":\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2022\/06\/cheesecake-labs-1.png\",\"contentUrl\":\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2022\/06\/cheesecake-labs-1.png\",\"caption\":\"Cheesecake Labs\",\"inLanguage\":\"en\"},\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/cheesecakelabs.com\/#primary-image\",\"url\":\"https:\/\/ckl-website-v4-strapi-prod.s3.us-east-2.amazonaws.com\/ai_software_development_company_83fb512983.webp\",\"contentUrl\":\"https:\/\/ckl-website-v4-strapi-prod.s3.us-east-2.amazonaws.com\/ai_software_development_company_83fb512983.webp\",\"width\":1920,\"height\":1080,\"caption\":\"Cheesecake Labs \u2014 AI, Data & Blockchain software development services\",\"inLanguage\":\"en\"},\"sameAs\":[\"https:\/\/www.facebook.com\/cheesecakelabs\",\"https:\/\/x.com\/cheesecakelabs\",\"https:\/\/www.instagram.com\/cheesecakelabs\/\",\"https:\/\/www.linkedin.com\/company\/cheesecake-labs\/\",\"https:\/\/www.youtube.com\/channel\/UCdGEQ5AHJcmIlaOaI5fGGVA\",\"https:\/\/clutch.co\/profile\/cheesecake-labs\",\"https:\/\/www.behance.net\/cheesecakelabs\",\"https:\/\/dribbble.com\/cheesecakelabs\",\"https:\/\/www.designrush.com\/agency\/profile\/cheesecake-labs\",\"https:\/\/www.g2.com\/products\/cheesecake-labs\/reviews\"],\"description\":\"Cheesecake Labs is a software development studio that designs and builds custom digital products \u2014 web, mobile, and platforms \u2014 combining product design and high-performance engineering.\",\"foundingDate\":\"2013\"},{\"@type\":\"Person\",\"name\":\"Bernardo Smaniotto\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cheesecakelabs.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2016\/11\/bernardo-300x300.jpg\",\"contentUrl\":\"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2016\/11\/bernardo-300x300.jpg\",\"caption\":\"Bernardo Smaniotto\"},\"description\":\"10 years of experience in Marketing and Sales in the Technology sector. My main purpose is help, support and structure efficient operations and also develop independent and multidisciplinary teams.\",\"url\":\"https:\/\/cheesecakelabs.com\/blog\/autor\/bernardo-2\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Django Migrations: What Really Annoys Me About Branching","description":"What makes Django migrations painful once branches diverge, why the generated files collide, and the workarounds worth using on a real project.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/","og_locale":"en_US","og_type":"article","og_title":"Django Migrations: What Really Annoys Me About Branching","og_description":"What makes Django migrations painful once branches diverge, why the generated files collide, and the workarounds worth using on a real project.","og_url":"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/","og_site_name":"Cheesecake Labs","article_publisher":"https:\/\/www.facebook.com\/cheesecakelabs","article_published_time":"2017-06-27T20:46:43+00:00","article_modified_time":"2026-08-15T07:56:27+00:00","og_image":[{"width":2000,"height":720,"url":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2017\/06\/Banner_database2.png","type":"image\/png"}],"author":"Cheesecake Labs","twitter_card":"summary_large_image","twitter_creator":"@cheesecakelabs","twitter_site":"@cheesecakelabs","twitter_misc":{"Written by":null,"Est. reading time":"7 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/#article","isPartOf":{"@id":"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/"},"author":{"name":"Bernardo Smaniotto"},"headline":"What really annoys me about Django migrations","datePublished":"2017-06-27T20:46:43+00:00","dateModified":"2026-08-15T07:56:27+00:00","mainEntityOfPage":{"@id":"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/"},"wordCount":1188,"commentCount":0,"publisher":{"@id":"https:\/\/cheesecakelabs.com\/blog\/#organization"},"image":{"@id":"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/#primaryimage"},"thumbnailUrl":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2017\/06\/Banner_database2.png","articleSection":["Product Engineering","Engineering"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/","url":"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/","name":"Django Migrations: What Really Annoys Me About Branching","isPartOf":{"@id":"https:\/\/cheesecakelabs.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/#primaryimage"},"image":{"@id":"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/#primaryimage"},"thumbnailUrl":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2017\/06\/Banner_database2.png","datePublished":"2017-06-27T20:46:43+00:00","dateModified":"2026-08-15T07:56:27+00:00","description":"What makes Django migrations painful once branches diverge, why the generated files collide, and the workarounds worth using on a real project.","breadcrumb":{"@id":"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/#primaryimage","url":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2017\/06\/Banner_database2.png","contentUrl":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2017\/06\/Banner_database2.png","width":2000,"height":720,"caption":"Django migrations"},{"@type":"BreadcrumbList","@id":"https:\/\/cheesecakelabs.com\/blog\/really-annoys-django-migrations\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cheesecakelabs.com\/blog\/"},{"@type":"ListItem","position":2,"name":"What really annoys me about Django migrations"}]},{"@type":"WebSite","@id":"https:\/\/cheesecakelabs.com\/blog\/#website","url":"https:\/\/cheesecakelabs.com\/blog\/","name":"Cheesecake Labs","description":"AI Implementation, Data and Product Engineering","publisher":{"@id":"https:\/\/cheesecakelabs.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cheesecakelabs.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cheesecakelabs.com\/blog\/#organization","name":"Cheesecake Labs","alternateName":"Cheesecake Labs Inc","url":"https:\/\/cheesecakelabs.com\/","logo":{"@type":"ImageObject","@id":"https:\/\/cheesecakelabs.com\/#logo","url":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2022\/06\/cheesecake-labs-1.png","contentUrl":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2022\/06\/cheesecake-labs-1.png","caption":"Cheesecake Labs","inLanguage":"en"},"image":{"@type":"ImageObject","@id":"https:\/\/cheesecakelabs.com\/#primary-image","url":"https:\/\/ckl-website-v4-strapi-prod.s3.us-east-2.amazonaws.com\/ai_software_development_company_83fb512983.webp","contentUrl":"https:\/\/ckl-website-v4-strapi-prod.s3.us-east-2.amazonaws.com\/ai_software_development_company_83fb512983.webp","width":1920,"height":1080,"caption":"Cheesecake Labs \u2014 AI, Data & Blockchain software development services","inLanguage":"en"},"sameAs":["https:\/\/www.facebook.com\/cheesecakelabs","https:\/\/x.com\/cheesecakelabs","https:\/\/www.instagram.com\/cheesecakelabs\/","https:\/\/www.linkedin.com\/company\/cheesecake-labs\/","https:\/\/www.youtube.com\/channel\/UCdGEQ5AHJcmIlaOaI5fGGVA","https:\/\/clutch.co\/profile\/cheesecake-labs","https:\/\/www.behance.net\/cheesecakelabs","https:\/\/dribbble.com\/cheesecakelabs","https:\/\/www.designrush.com\/agency\/profile\/cheesecake-labs","https:\/\/www.g2.com\/products\/cheesecake-labs\/reviews"],"description":"Cheesecake Labs is a software development studio that designs and builds custom digital products \u2014 web, mobile, and platforms \u2014 combining product design and high-performance engineering.","foundingDate":"2013"},{"@type":"Person","name":"Bernardo Smaniotto","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cheesecakelabs.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2016\/11\/bernardo-300x300.jpg","contentUrl":"https:\/\/ckl-website-static.s3.amazonaws.com\/wp-content\/uploads\/2016\/11\/bernardo-300x300.jpg","caption":"Bernardo Smaniotto"},"description":"10 years of experience in Marketing and Sales in the Technology sector. My main purpose is help, support and structure efficient operations and also develop independent and multidisciplinary teams.","url":"https:\/\/cheesecakelabs.com\/blog\/autor\/bernardo-2\/"}]}},"_links":{"self":[{"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/posts\/4917","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/users\/65"}],"replies":[{"embeddable":true,"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/comments?post=4917"}],"version-history":[{"count":2,"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/posts\/4917\/revisions"}],"predecessor-version":[{"id":14820,"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/posts\/4917\/revisions\/14820"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/media\/4951"}],"wp:attachment":[{"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/media?parent=4917"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/categories?post=4917"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cheesecakelabs.com\/blog\/wp-json\/wp\/v2\/tags?post=4917"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}