Seamless Transition: How I Converted an Existing MySQL Database to be Fully with Daniel Ramas

This video features Daniel Ramas at DjangoCon US 2024 in Durham, North Carolina, USA.

Seamless Transition: How I Converted an Existing MySQL Database to be Fully with Daniel Ramas
0:21:41
Published December 6, 2024
123 views

The full title of this talk is "Seamless Transition: How I Converted an Existing MySQL Database to be Fully Managed by Django Migrations Framework"

In this presentation, I aim to demystify the complexities of database migrations in Django, catering to audiences with basic knowledge of the framework. Through a structured approach, I will delve into three key topics:

  • Understanding Django's Migration Process: I will elucidate how Django determines and executes migrations, shedding light on the underlying mechanisms that govern this process.
  • Managing Dependencies in Migration Files: Delving deeper, I'll explore how dependencies within migration files impact the migration process, offering insights into best practices to navigate potential challenges.
  • Practical Steps for Migrating an Existing Database: Leveraging my own experience, I will guide attendees through a step-by-step methodology for migrating an existing database to Django. This will include:
  1. Ensuring consistency in the id field type and configuring the DEFAULT_AUTO_FIELD setting accordingly. I'll also address strategies for handling inconsistencies.
  2. Utilizing 'manage.py inspectdb' to generate models and incorporating them into the 'models.py' file.
  3. Transitioning models from 'managed=False' to 'managed=True' to initiate migration tracking.
  4. Handling existing Many-to-Many Relationships seamlessly.
  5. Generating initial migration files with 'manage.py makemigrations' and faking the initial migration with 'manage.py migrate --fake'.
  6. Optionally, creating ForeignKey fields and enforcing backend foreign key relationships.
  7. Addressing the cleanup of orphaned columns in preparation for conversion to Foreign Keys.

This talk will be structured with slides covering the first two topics, followed by a practical demonstration of the third topic through real-world examples and code snippets.

It's important to note that while my methodology was successful with a MySQL database, variations may occur with other database languages. In the event of selection, I am committed to collaborating with a mentor to adapt the content for broader applicability.

This talk was presented at: https://2024.djangocon.us/talks/seamless-transition-how-i-converted-an-existing-mysql-database-to-be-fully-managed-by-django-migrations-framework/

LINKS:
Follow Daniel Ramas 👇
Website: http://www.github.com/Daniel-Ramas

Follow DjangoCon US 👇
https://fosstodon.org/@djangocon
https://x.com/djangocon

Follow DEFNA 👇
https://www.defna.org/

Video production by the presenter and DjangoCon US 2024 volunteers.

Summary

Daniel Ramos explains how he moved an existing MySQL database into Django’s migration system after converting a Laravel application to Django. Django generates migrations by comparing model definitions with migration history, not by inspecting the live database, so he covers how to use `inspectdb`, set `DEFAULT_AUTO_FIELD`, make inspected models managed, add many-to-many relationships, and mark initial migrations as applied with `migrate --fake` or `--fake-initial`. He also shows how to repair data before adding database-enforced foreign keys with a custom `RunPython` migration, and warns that changing a normal field into a foreign key can make Django remove and recreate a column unless the migration is edited to preserve the column with `AlterField`, `RenameField`, and the correct `db_column`.

Key takeaways

  • Django builds migrations from `models.py` and migration history rather than inspecting the current database schema.
  • Use `inspectdb` to generate models, match `DEFAULT_AUTO_FIELD` to existing primary-key types, and set inspected models to `managed = True`.
  • For an existing schema, use `migrate --fake` or `migrate --fake-initial` so Django records initial migrations without trying to recreate tables.
  • Clean orphaned or invalid data in a custom `RunPython` migration before adding a database-enforced foreign key.
  • Review generated migrations when converting fields to foreign keys, using `db_column`, `AlterField`, and `RenameField` where needed to avoid losing an existing column and its data.

Summarised automatically from the transcript.

Transcript

2,720 words · auto-generated Show

Automatically transcribed, so expect mistakes in names and technical terms.

0:14

Carbon

0:20

Speaker 1: Hello, thank you all for attending my talk. Today I'll be sharing an experience I had with a project I worked on earlier this year where I converted an existing MySQL database to be fully managed by Django's migration framework. Uh a little bit about myself. My name is Daniel Ramos. I am from Los Angeles, California. I've been a Django and view developer for about four years now, and I currently work for a company called Horizon Realty Advisors. Horizon is a Seattle-based company that's a market leader and owner and operator of multifamily housing with a portfolio spanning uh 57 locations across 14 states And this includes both student and conventional housing.

1:06

Speaker 1: And this will also be my third DjangoCon attended. I attended 2022 online and 2023 in person. Uh entering my current role, I was more of a front-end and backend developer with limited exposure to the database side of things. And in August 2022, I was tasked with converting a Laravel backend web application to Django. And getting the application up and running was pretty easy. I discovered Inspect DB, which was a super useful utility to create the model files. But one of the biggest roadblocks I had came whenever I needed to start running migrations. I was getting errors about tables already existing.

1:52

Speaker 1: fields being named wrong, incorrect column types, orphaned rows, and that's just to name a few. And I'll be first to admit that I didn't spend a lot of time reading document Prior to this project, the only knowledge of migrations I had was, you know, if you run make migrations, it'll create some file, and then if you run migrate, it'll execute. that script. In order to convert the existing MySQL database, I had no choice but to get familiar with what was going on under the hood. So how does Django create and generate migration files? Uh this is Django 's description of the migration system. From the Django Project. com website, and it says

2:38

Speaker 1: migrations are Django's way of propagating changes you make to your models, adding fields, deleting a model, etc. into your database schema. So how does Django create and generate migrations files? Since I never really read the documentation, for some reason I assumed that Django could Look inside the database and generate a migration file off that. So to my surprise, Django doesn't actually look at the database when generating migrations. It looks at two things the models. py file and the migration history See the migration history allows for the migration framework to build a current state of what the database should be looking like, compare it against the models. py file and generate a migration file based off that.

3:23

Speaker 1: So if you look at this uh diagram that I made, when you run make migrations, um it'll look at the migrations folder, compare it against the state of models. py, create new migrations, and then decide what to execute based on the migrations that have been saved in the Django. migrations table. So let's take a look at an empty migration file to get familiar with what's going on inside The empty migration file contains a class named a migration, and typically you'll see two class attributes already there, dependencies and operations. Operations is a list of uh actions to be ran on the database.

4:09

Speaker 1: And aside from the stuff that Django generates, we have the ability to run custom Python scripts. Uh and we can do that using Run Python. So for the forward operation of RunPython It accepts uh quite a few arguments, but the two that I've seen most common are apps and schema editor, and that's the same for the reverse function. And apps is a Django application registry that stores information of the installed applications. And by far the most common use case of apps object is using the get model. method which is a method that returns a specific model at the current state of the migration history.

4:56

Speaker 1: This is something that gave me a ton of trouble at first because I assumed that the model would be synced with the models. py and I would even try to directly import the models into a migration file. And the fact that we have to use apps. get model makes a ton of sense because Uh if let's say you alter a field on a model after creating an empty migration, that field doesn't exist yet, so it may not uh exist in this state of the history. And uh schema editor, the other uh argument to the forward and reverse functions It can be used to alter database schema like creating fields, creating tables, etc. but I've personally never had to use this. I think Django's migration framework is robust enough to handle a lot of those changes that you might want to use

5:47

Speaker 1: schema editor for. Uh the other common uh migration class that you could use is Run SQL and RunSQL accepts strings of SQL commands to run on the run on the database. Of course be careful when using this. Make sure you sanitize your inputs and Don't uh make yourself uh susceptible to uh sequel injection in this area So uh this brings me to dependencies. Uh and one pattern you may have noticed is that migrations have a dependency to the previous migration that is applied before it.

6:33

Speaker 1: This is how uh uh Django links migrations and is able to create a tree to uh describe what the migration history looks like. By far the most common way I've seen other migrations be dependencies other than the previous migration in the same app is when you create foreign queue relationships between models. So if you look at this code snippet, the employee model is in the core app and we're creating a migration and a hypothetical app called sales. And in order to create the foreign key relationship, you need to add core as a dependency in this migration. So now that we're familiar with migration files, let's go through a example of how to sync an existing data.

7:23

Speaker 1: base to Django. So uh in this project I'll be using Docker Compose for my development environment but um using virtual and uh virtual environment is fine too. So right now I have an e empty Django project with the basic migrations already ran and my database is existing. I got this off the MySQL website. So uh using my database browser, we can see here that We have an existing database here. It's called Employees, where it has departments, employees, salaries, titles, things like that as full uh populated with a bunch of dummy data. And in settings.

8:09

Speaker 1: py I've created uh the connection here as the default database to that um to this to this database So the first thing you want to do is set the default auto field setting. And if we scroll down here, it should be at the bottom. Uh go ahead and take a look at your database and take note of the type of the primary key fields. Uh doing this will create consistency when we decide to create more tables in the future. So for this project, I took a look at the database and the primary keys are primarily integers. So we could change this to be auto field, which is a subclass of integer fields.

8:56

Speaker 1: So uh next thing we want to do is use Inspect DB to generate the models. So if we stop the development server and we run inspect db and we output it to a file in the core uh folder and we call it models. py. It will generate a models. py file based on the database schema it was able to detect And so for here, uh because I've already ran Django migrations, we can actually delete uh quite a few of these. I'll delete them right now

9:52

Speaker 1: Okay, so uh these are gonna be the uh models from the example database And next thing we want to do is we want to flip manage equals false to manage equals true because we want Django to be managing these uh database tables Okay, so next thing we're gonna do is we're gonna add many to many relationships As we can tell, we have a couple mapping tables here, department employee and department manager. What we could do is for um to make things easier whenever we're creating uh queries, we can create many to menu fields here and we let's just put them on the

10:37

Speaker 1: departments model And we're gonna explicitly define this related name And we'll call this one managers. And we'll call this related name department manager Next thing we're gonna do is we're gonna create initial migration files. So if we run python manage. py Make migrations on the core app. We will create an initial migration file

11:26

Speaker 1: Uh okay, so what we can do next is we can actually apply these migrations, but if we were to try to apply these migrations We will get an error for pretty much saying like some of these tables already exist, which they do because this is an existing MySQL database. Well we need to do to bypass that because we do need this uh these migrations to be in the migration history, we can just run python manage. py migrate fake and essentially this will what this will do is it will fake all migrations that need to be applied. There's also a command Manage. py migrate

12:12

Speaker 1: fake initial and we can use this if we have multiple migrations we need to run, but only the first initial migration need to be uh fake. Uh so the last thing I wanted to discuss was uh foreign keys that are enforced on uh backend application rather than on the database. Um for um for myself whenever I was converting to PHP application into Django, I was able to see on the source code there were certain models that had foreign key fields that were enforced on the back end but not on the database And therefore whenever I ran inspect DB, I wasn't able to find uh certain fields to be a foreign key when it was supposed to be.

12:59

Speaker 1: And for this example If we look at uh employee info, uh it's safe to assume that this employee number field should be a foreign key to employees. And so if we just change this, um since this is a primary key, it should be a one-to-one field. And we can point it to the employees model and define the on delete behavior And then define the D B column name, which is employee number. Okay. And if we create the migrations file using make migrations

13:51

Speaker 1: We can go ahead and run this migration since we want to have this field be indexed on the database, but we'll notice something interesting might happen. So we get an error here. Cannot add or update a child row. A foreign key constraint fails Uh foreign key employee number references employees and so uh pretty much what this means is that uh There is an employee info object that has an employee number that doesn't exist in the employees database. And Um this can be a result of you know mismanaging the on-delete behaviors.

14:37

Speaker 1: Um it could be a number of things, but If we do want to have this foreign key field indexed on the database and we want to have this migration RAND, what we could do is we can actually create a Custom migration. We can have it clean the employee info table and remove any employee numbers that don't point to a existing employee. And so if I delete this uh migration uh I will Uh do make migrations core and create an empty migration

15:23

Speaker 1: Um I'm gonna use uh run Python to do the uh cleaning of the columns And then for the reverse function of another one. And uh here we can uh define the functions. And then we can do something like Ploe equals apps dot get model.

16:08

Speaker 1: And we can get the employee info. Uh and then we can create an ORM query like So all this query is doing is it's getting any instance That doesn't have an employee number that points to the employee number on the employee table. Um this will be a one-way uh operation so there's gonna be no way to get this uh get those deleter rows back and so uh since this is uh Operation that can't be reversed.

16:53

Speaker 1: We actually don't really need to have a reverse uh function. We can just do migrations run Python no op. And so now what this will do is this will clean that column, the employee number column in the employee info table. And so If we run migrate and then if we do make migrations again So we could see here that uh we're gonna be converting this to a one-to-one field. And if we run uh migrate

17:41

Speaker 1: It should apply just fine. Uh so this last thing I wanted to go over was a specific scenario where Let's say we have this uh model blog post. I just uh created the migration for it and so now it'll be in the migration history. But let's say uh we get a field called employee underscore ID although the other fields were called employee number but let's just say this one's employee ID and we wanted to convert this to be a foreign key So if we do foreign key and then we point it to employees and we define the on-delete behavior And uh let's

18:26

Speaker 1: uh let's provide it a default so it doesn't give us an error. Um If we do this and we just want to call it employee and we create the migration If you look at the migration that created, it actually deletes the field employee ID and creates a new one. And this is something to be uh very careful over. You would uh be essentially deleting that column and losing all that information. Uh my way of fixing this would be to change these to be an alter field rather than remove an add field. So we can just go ahead and remove this

19:12

Speaker 1: remove field and change this to be an alter field Uh and then we will just uh change this to the field name that we had, so employee ID. Um

19:27

Speaker 2: hey everybody, this is Daniel.

19:30

Speaker 1: I'm actually in the middle of editing this video and I wanted to make a correction to this last section To change the employee ID field into a foreign key, you're actually going to want to include uh this db column uh argument just to map it to the employee id field otherwise it would be called something like employee id underscore id And to just change employee ID to be employee, you're gonna want to use rename field and rename employee ID to employee. Now if we run these migrations.

20:17

Speaker 1: Uh the migrations will apply and if we take a look at the database we can see that the Employee ID is now a foreign key to employee number and there's no issue with the column name. So to summarize, uh The steps you need to take to convert an existing database to be fully managed by Django's migration framework is number one, you need to set the default auto field to the primary key types of the primary keys of the existing existing database. Then you can use PythonManage. py inspectDB to create the models. py file. And then after that add many to many fields if there are any many to many fields you want to include. Then you need to fake those migrations to have those models and migrations be in the migration history.

21:05

Speaker 1: And then optionally you can create foreign key relationships for back and enforce foreign keys. Uh thank you all for attending my talk today and I hope you all have a great rest of your week. Thanks. And I 'll be And then what will you want on them?

Questions this talk answers

How does Django decide what migrations to generate?

Django compares the current models.py state with the recorded migration history, rather than inspecting the database directly. It uses that comparison to create migration files and determine what should be applied.

Discussed at 2:38

How do I choose Django's default auto field when connecting to an existing database?

Inspect the existing database's primary-key column types and set `DEFAULT_AUTO_FIELD` to match them. For the example database, the primary keys were integers, so `AutoField` was appropriate.

Discussed at 8:09

How do I use InspectDB to convert an existing MySQL schema into Django models?

Run `python manage.py inspectdb` and output the result to the app's models.py file. Then adjust the generated models, including setting `managed = True` and adding any desired many-to-many relationships.

Discussed at 8:56

How do I make Django migrations work with an existing database whose tables already exist?

Generate the initial migrations from the imported models, then use `python manage.py migrate --fake` so Django records them as applied without trying to recreate the existing tables. If only the initial migration needs to be faked, use `migrate --fake-initial`.

Discussed at 11:26

How do I add a foreign key to an existing database when the relationship is only enforced by the application?

Update the Django model with the appropriate relationship, delete behavior, and database column name, then create and run the migration. If it fails because orphaned rows violate the constraint, use a custom `RunPython` migration to remove or repair those rows before adding the foreign key.

Discussed at 12:59

How do I clean orphaned rows before adding a foreign key in a Django migration?

Create an empty migration and use `RunPython` with `apps.get_model()` to find rows whose referenced employee does not exist, then delete or repair them. Since this cleanup cannot be reversed, the reverse operation can be `migrations.RunPython.noop`.

Discussed at 15:23

How do I convert an existing database column into a Django foreign key without losing its data?

Do not let Django generate a remove-and-add operation, because that would drop the existing column. Edit the migration to use `AlterField`, preserve the existing column with `db_column`, and use `RenameField` if the Django field name is changing.

Discussed at 19:30

What are the steps for fully managing an existing database with Django migrations?

Match `DEFAULT_AUTO_FIELD` to the existing primary keys, generate models with `inspectdb`, add any desired many-to-many and foreign-key relationships, create migrations, and fake the initial migrations so they enter Django's migration history without recreating existing tables.

Discussed at 20:17

Presenters

Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.

More videos from DjangoCon US