Programming
1. rename app
#!/usr/bin/env python3
"""
Rename the `portfolio` app files to `loanbook` (or any other name).
Run it once, from your project root, AFTER copying the files into the app
directory and BEFORE the first `makemigrations`.
python rename_app.py loanbook
What it changes:
* absolute imports from portfolio.bridge -> from loanbook.bridge
* template lookups "portfolio/base.html" -> "loanbook/base.html"
* URL namespaces {% url 'portfolio:x' %} -> {% url 'loanbook:x' %}
* static paths 'portfolio/portfolio.css' -> 'loanbook/loanbook.css'
* apps.py `name`, urls.py `app_name`, the Plotly PNG download filename
* directory names templates/portfolio/, static/portfolio/
It does NOT touch your settings.py or root urls.py — those are Step 3 and 4,
and they are one line each.
"""
from __future__ import annotations
import re
import shutil
import sys
from pathlib import Path
OLD = "portfolio"
TEXT_SUFFIXES = {".py", ".html", ".css", ".txt", ".md", ".json", ".cfg"}
SKIP_DIRS = {"__pycache__", ".git", "node_modules", ".venv", "venv", "migrations"}
# Human-facing strings that should keep the word "portfolio" — renaming these
# would produce nonsense like "Credit loanbook analytics".
KEEP_VERBATIM = (
"Credit portfolio analytics",
"credit portfolio",
"loan portfolio",
)
def rewrite(text: str, new: str) -> tuple[str, int]:
placeholders = {}
for i, phrase in enumerate(KEEP_VERBATIM):
token = f"\x00KEEP{i}\x00"
if phrase in text:
placeholders[token] = phrase
text = text.replace(phrase, token)
text, n = re.subn(rf"\b{OLD}\b", new, text)
for token, phrase in placeholders.items():
text = text.replace(token, phrase)
return text, n
def main(new: str, root: Path) -> int:
app_dir = root / new
if not app_dir.is_dir():
print(f"! no directory {app_dir} — create the app first, then copy the files in")
return 1
# 1. Rename the nested template/static directories.
for parent in ("templates", "static"):
old_dir = app_dir / parent / OLD
new_dir = app_dir / parent / new
if old_dir.is_dir():
if new_dir.exists():
print(f"! {new_dir} already exists — merge it yourself and re-run")
return 1
old_dir.rename(new_dir)
print(f" dir {parent}/{OLD}/ -> {parent}/{new}/")
# 2. Rename the stylesheet so the file matches the app.
css_old = app_dir / "static" / new / f"{OLD}.css"
if css_old.is_file():
css_old.rename(app_dir / "static" / new / f"{new}.css")
print(f" file static/{new}/{OLD}.css -> {new}.css")
# 3. Rewrite file contents.
total_files = total_hits = 0
for path in sorted(app_dir.rglob("*")):
if not path.is_file() or path.suffix not in TEXT_SUFFIXES:
continue
if any(part in SKIP_DIRS for part in path.parts):
continue
original = path.read_text(encoding="utf-8")
updated, n = rewrite(original, new)
if n:
path.write_text(updated, encoding="utf-8")
total_files += 1
total_hits += n
print(f" edit {path.relative_to(root)} ({n})")
# 4. Drop any pre-baked migration; it carries the old app label.
stale = [p for p in (app_dir / "migrations").glob("0*.py")]
for p in stale:
p.unlink()
print(f" del {p.relative_to(root)} (regenerate with makemigrations)")
# 5. Clear caches so stale .pyc files don't shadow the rename.
for cache in app_dir.rglob("__pycache__"):
shutil.rmtree(cache, ignore_errors=True)
print(f"\ndone: {total_hits} replacements across {total_files} files")
print("\nnext:")
print(f" 1. add \"{new}\" to INSTALLED_APPS")
print(f" 2. add path(\"{new}/\", include(\"{new}.urls\")) to your root urls.py")
print(f" 3. python manage.py makemigrations {new} && python manage.py migrate")
return 0
if __name__ == "__main__":
name = sys.argv[1] if len(sys.argv) > 1 else "loanbook"
if not re.fullmatch(r"[a-z][a-z0-9_]*", name):
print("app name must be a valid lowercase Python identifier")
raise SystemExit(1)
raise SystemExit(main(name, Path.cwd()))
PO
powerty
Author
· Staff
Aug. 26, 2026
Aug. 26, 2026
4
Views
0
Likes
3m
Read