Thanks to visit codestin.com
Credit goes to github.com

Skip to content
/ django Public

Fixed #27489 -- Renamed permissions upon model renaming in migrations.#20539

Open
artirix1927 wants to merge 1 commit intodjango:mainfrom
artirix1927:ticket_27489_fix2
Open

Fixed #27489 -- Renamed permissions upon model renaming in migrations.#20539
artirix1927 wants to merge 1 commit intodjango:mainfrom
artirix1927:ticket_27489_fix2

Conversation

@artirix1927
Copy link
Contributor

@artirix1927 artirix1927 commented Jan 15, 2026

ticket-27489

AI Assistance Disclosure (REQUIRED)

  • No AI tools were used in preparing this PR.
  • If AI tools were used, I have disclosed which ones, and fully reviewed and verified their output.

GPT-5 Mini was used to write a verbose prints and warning catching tests, reviewed the output carefully.

Checklist

  • This PR follows the contribution guidelines.
  • This PR does not disclose a security vulnerability (see vulnerability reporting).
  • This PR targets the main branch.
  • The commit message is written in past tense, mentions the ticket number, and ends with a period.
  • I have checked the "Has patch" ticket flag in the Trac system.
  • I have added or updated relevant tests.
  • I have added or updated relevant docs, including release notes if applicable.
  • I have attached screenshots in both light and dark modes for any UI changes.

Copy link
Member

@jacobtylerwalls jacobtylerwalls left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I copied over some findings that I had from working on #20481.

Comment on lines 1551 to 1560
ct = ContentType.objects.create(app_label="auth_tests", model="oldmodel")
actions = ["add", "change", "delete", "view"]
for action in actions:
Permission.objects.create(
codename=f"{action}_oldmodel",
name=f"Can {action} old model",
content_type=ct,
)

call_command("migrate", "auth_tests", verbosity=0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be simpler & model some realism to rely on the behavior of the post_migrate handler, so just migrate one step:

Suggested change
ct = ContentType.objects.create(app_label="auth_tests", model="oldmodel")
actions = ["add", "change", "delete", "view"]
for action in actions:
Permission.objects.create(
codename=f"{action}_oldmodel",
name=f"Can {action} old model",
content_type=ct,
)
call_command("migrate", "auth_tests", verbosity=0)
# Create initial content type and permissions for OldModel.
call_command("migrate", "auth_tests", "0001", verbosity=0)
# Apply the migration that renames OldModel to NewModel.
call_command("migrate", "auth_tests", "0002", verbosity=0)
actions = ContentType._meta.default_permissions

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't want to force you to do it my way, but after merging the original version Tim Graham observed without comments it was difficult to follow what was going on, so I think keeping the suggested comments here will help.

@artirix1927 artirix1927 force-pushed the ticket_27489_fix2 branch 3 times, most recently from 6531c21 to 176a899 Compare February 3, 2026 23:36
Copy link
Member

@jacobtylerwalls jacobtylerwalls left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Appreciate the updates!

Comment on lines 1551 to 1560
ct = ContentType.objects.create(app_label="auth_tests", model="oldmodel")
actions = ["add", "change", "delete", "view"]
for action in actions:
Permission.objects.create(
codename=f"{action}_oldmodel",
name=f"Can {action} old model",
content_type=ct,
)

call_command("migrate", "auth_tests", verbosity=0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't want to force you to do it my way, but after merging the original version Tim Graham observed without comments it was difficult to follow what was going on, so I think keeping the suggested comments here will help.

new_name_str = f"Can {action} {verbose_name_raw}"

if perm.codename == new_codename and perm.name == new_name_str:
continue
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is missing a test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You think its better to mock the save and test it that way?
Or get the permission and check that the pk says the same which means its same permission (hope that makes sense to you) ?

Copy link
Member

@jacobtylerwalls jacobtylerwalls Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not clear to me whether this condition ever evaluates True. We would need a perm in the database with a codename that __endswith the old codename but also == the new_codename, so that would either happen because of:

  • substring matches (which I think you said you were looking into)
  • or MODEL and model both mapping to model

So you could add a test case for a MODEL being renamed to model (seems silly, but might happen on legacy dbs, I guess). Or revisit this depending on how you remove the substring matching.

If it's not reachable, but you want to add a sanity check in the code, I'd be open to adding an assert.


for _, from_codename, to_codename, _ in planned:
if verbosity >= 2:
print(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also missing a test.

Comment on lines 214 to 219
print(
style.WARNING(
f"Failed to rename permission {pk} "
f"from '{old}' to '{new}'. "
f"Please resolve the conflict manually."
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to capture this output in the test with captured_stdout and assert over it, see test run:

.........Failed to rename permission 463 from 'change_oldmodel' to 'change_newmodel'. Please resolve the conflict manually.
....
----------------------------------------------------------------------
Ran 117 tests in 2.015s



def _get_permission_metadata(apps, app_label, model_name):
Permission = apps.get_model("auth", "Permission")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should get the default permissions defined on the model being renamed, not the Permission model itself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get them on the model being renamed if its available. If we have lookup error I use the permission model default permissions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha, sorry. In that case, I wonder if we should:

  • move this line into the except
  • or, just handle LookupError a lot earlier instead of inside this util

create_permissions() returns very early if there's a LookupError.


perms = Permission.objects.using(db).filter(
content_type__app_label=app_label,
codename__endswith=old_suffix,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like the first version with icontains, I'm still concerned about substring conflation, can we avoid using endswith somehow?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can just prepare the whole codename and just codename = looking_for_codename. I don't think there is another way to filter permissions other than strings.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@jacobtylerwalls
Copy link
Member

I think sometimes the GitHub UI isn't super helpful showing replies. I replied to a comment from a previous review: here
#20539 (comment)

In that review, I added a suggestion to replace the manual content type creations with stepwise migrations, e.g. 0001, then 0002:

        # Create initial content type and permissions for OldModel.
        call_command("migrate", "auth_tests", "0001", verbosity=0)
        # Apply the migration that renames OldModel to NewModel.
        call_command("migrate", "auth_tests", "0002", verbosity=0)

You applied that suggestion (thank you!) but without the comments. So, unless you're deeply familiar with the content type internals (I certainly wasn't!) it's not clear that there's a side effect of permissions being created. All I meant to indicated was to keep the comments I put in my suggestion.

You're doing a great job being responsive to all the feedback, this is a tricky area for several reasons and explains why the ticket has been open so long. I'm very positive on the positive of landing this soon 🤝

@artirix1927 artirix1927 force-pushed the ticket_27489_fix2 branch 2 times, most recently from e013d89 to d729f81 Compare February 11, 2026 00:07
@github-actions
Copy link

📊 Coverage Report for Changed Files

-------------
Diff Coverage
Diff: origin/main...HEAD, staged and unstaged changes
-------------
django/contrib/auth/apps.py (100%)
django/contrib/auth/management/__init__.py (98.3%): Missing lines 147
-------------
Total:   62 lines
Missing: 1 line
Coverage: 98%
-------------


Note: Missing lines are warnings only. Some lines may not be covered by SQLite tests as they are database-specific.

For more information about code coverage on pull requests, see the contributing documentation.

@artirix1927 artirix1927 force-pushed the ticket_27489_fix2 branch 3 times, most recently from 63e5bf8 to 2c74929 Compare February 14, 2026 03:31
@artirix1927
Copy link
Contributor Author

I think sometimes the GitHub UI isn't super helpful showing replies. I replied to a comment from a previous review: here #20539 (comment)

In that review, I added a suggestion to replace the manual content type creations with stepwise migrations, e.g. 0001, then 0002:

        # Create initial content type and permissions for OldModel.
        call_command("migrate", "auth_tests", "0001", verbosity=0)
        # Apply the migration that renames OldModel to NewModel.
        call_command("migrate", "auth_tests", "0002", verbosity=0)

You applied that suggestion (thank you!) but without the comments. So, unless you're deeply familiar with the content type internals (I certainly wasn't!) it's not clear that there's a side effect of permissions being created. All I meant to indicated was to keep the comments I put in my suggestion.

You're doing a great job being responsive to all the feedback, this is a tricky area for several reasons and explains why the ticket has been open so long. I'm very positive on the positive of landing this soon 🤝

I noticed that my recent PR is failing during the CI/CD stage due to a PostgreSQL connection error.

It also seems that other PRs are experiencing similar failures today, so I wanted to check whether this is a known infrastructure issue or something caused by my changes.

Could you please confirm whether this is on my side or related to the CI setup?

Thank you.

@jacobtylerwalls
Copy link
Member

Nothing to do with your changes -- there have been a lot of infrastructure problems on CI recently. The GitHub actions have been more resilient than the Jenkins actions, so I would just pay attention to that feedback for now. Sorry.

Changed the pre migrate signal and inserting
permission rename operations in the migration
plan to a post migrate signal.

When having conflicting permission rename,
added a note for user with next steps.
Avoiding integrity error by finding
conflicting perms in advance and skipping
them.

Added taking permission actions and
verbose name raw from model class
when available.

Added using full literal codename for
filtering instead of using substings
with endswith/startswith. Hence
deleted dead condition for checking
if permission is already correct.

Added stdout for instead of prints
for logging.

Brought back respects other db test.
Improved some tests by calling
migrate 0001 instead of creating
permissions manually.

Added verbosity prints test.
Added warning msg assertion for
conflicting permissions test.

Updated general docs and 6.1 release docs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants