When I initially created my MVP (minimal viable product) for 1st things 1st, I considered the whole Django project to be about prioritization. After a few years, I realized that the Django project is about SaaS (software as a service), and prioritization is just a part of all functionalities necessary for a SaaS to function. I ended up needing to rename apps to have clean and better-organized code. Here is how I did that.
0. Get your code and database up to date
Ensure you have the latest git pull and execute all database migrations.
1. Install django-rename-app
Put django-rename-app
into pip requirements and install them or just run:
(venv)$ pip install django-rename-app
Put the app into INSTALLED_APPS
in your settings:
INSTALLED_APPS = [
# …
"django_rename_app",
]
2. Rename the app directories
Rename the oldapp
as newapp
in your apps and templates.
3. Rename the app name occurrences in the code
Rename the app in all your imports, relations, migrations, and template paths.
You can do a global search for oldapp
and then check case by case where you need to rename that term to newapp
, and where not.
4. Run the management command rename_app
Run the management command rename_app
:
(env)$ python manage.py rename_app oldapp newapp
This command renames the app prefix the app tables and the records in django_content_type
and django_migrations
tables.
If you plan to update staging or production servers, add the rename_app
command before running migrations in your deployment scripts (Ansible, Docker, etc.)
5. Update indexes and constraints
Lastly, create an empty database migration for the app with custom code to update indexes and foreign-key constraints.
(env)$ python manage.py makemigrations newapp --empty --name rename_indexes
Fill the migration with the following code:
# newapp/migrations/0002_rename_indexes.py
from django.db import migrations
def named_tuple_fetch_all(cursor):
"Return all rows from a cursor as a namedtuple"
from collections import namedtuple
desc = cursor.description
Result = namedtuple("Result", [col[0] for col in desc])
return [Result(*row) for row in cursor.fetchall()]
def rename_indexes(apps, schema_editor):
from django.db import connection
with connection.cursor() as cursor:
cursor.execute(
"""SELECT indexname FROM pg_indexes
WHERE tablename LIKE 'newapp%'"""
)
for result in named_tuple_fetch_all(cursor):
old_index_name = result.indexname
new_index_name = old_index_name.replace(
"oldapp_", "newapp_", 1
)
cursor.execute(
f"""ALTER INDEX IF EXISTS {old_index_name}
RENAME TO {new_index_name}"""
)
def rename_foreignkeys(apps, schema_editor):
from django.db import connection
with connection.cursor() as cursor:
cursor.execute(
"""SELECT table_name, constraint_name
FROM information_schema.key_column_usage
WHERE constraint_catalog=CURRENT_CATALOG
AND table_name LIKE 'newapp%'
AND position_in_unique_constraint notnull"""
)
for result in named_tuple_fetch_all(cursor):
table_name = result.table_name
old_foreignkey_name = result.constraint_name
new_foreignkey_name = old_foreignkey_name.replace(
"oldapp_", "newapp_", 1
)
cursor.execute(
f"""ALTER TABLE {table_name}
RENAME CONSTRAINT {old_foreignkey_name}
TO {new_foreignkey_name}"""
)
class Migration(migrations.Migration):
dependencies = [
("newapp", "0001_initial"),
]
operations = [
migrations.RunPython(rename_indexes, migrations.RunPython.noop),
migrations.RunPython(rename_foreignkeys, migrations.RunPython.noop),
]
Run the migrations:
(env)$ python manage.py migrate
If something doesn't work as wanted, migrate back, fix the code, and migrate again. You can unmigrate by migrating to one step before the last migration, for example:
(env)$ python manage.py migrate 0001
6. Cleanup
After applying the migration in all necessary environments, you can clean them up by removing django-rename-app
from your pip requirements and deployment scripts.
Final words
It's rarely possible to build a system that meets all your needs from the beginning. Proper systems always require continuous improvement and refactoring. Using a combination of Django migrations and django-rename-app
, you can work on your websites in an Agile, clean, and flexible way.
Happy coding!
Cover photo by freestocks.
No comments:
Post a Comment