Opened 17 months ago
Closed 5 months ago
#34608 closed Uncategorized (invalid)
Migrations generates code that it can't execute
Reported by: | Michael | Owned by: | nobody |
---|---|---|---|
Component: | Migrations | Version: | 5.0 |
Severity: | Normal | Keywords: | |
Cc: | Triage Stage: | Unreviewed | |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
Migrations have been fine, until I rebuilt them to squash them down.
When I run makemigrations
it generates this line of code:
bases=(core.djpp.permpp.PermissionsMixin_factory.<locals>.PermissionsMixin, models.Model, djpp.modelpp.NiceNameMixin),
Which when it tries to run falls over:
File "/home/user/project/src/dist/app/plug/migrations/accounts/0001_initial.py", line 92 bases=(core.djpp.permpp.PermissionsMixin_factory.<locals>.PermissionsMixin, models.Model, djpp.modelpp.NiceNameMixin), ^ SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
Here is most of the migration, I left out some other operations:
# Generated by Django 4.0.1 on 2023-05-30 16:17 import accounts.models import core.djpp.permpp import django.contrib.auth.models from django.db import migrations, models import django.utils.timezone import djpp.modelpp import lib.timelib.ttb class Migration(migrations.Migration): initial = True dependencies = [ ('auth', '0012_alter_user_first_name_max_length'), ] operations = [ ..., migrations.CreateModel( name='User', fields=[ ('id', models.SmallAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('password', models.CharField(max_length=128, verbose_name='password')), ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), ('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')), ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='admin access')), ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), ], options={ 'db_table': 'auth_user', }, bases=(core.djpp.permpp.PermissionsMixin_factory.<locals>.PermissionsMixin, models.Model, djpp.modelpp.NiceNameMixin), managers=[ ('objects', accounts.models.UserManager()), ], ), ]
In Django's defense it is quite a convoluted iheritance chain for the user model, that looks like this:
#permpp.py - permission mixin factory which the user model inherits from def PermissionsMixin_factory(group_roles: GroupRoles, user_perms: UserPerms) -> type: class PermissionsMixin: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # user.has_perm is built into Django, call ours user.permissions setattr(self, GETTER_ATTR, PermissionGetter(self, group_roles, user_perms)) @property def cached_groups(self): print('cached_group things') return PermissionsMixin PermissionsMixin = permpp.PermissionsMixin_factory(grs, ups)
#perm.py - Constructs the permission mixin from core.djpp import permpp ... PermissionsMixin = permpp.PermissionsMixin_factory(...)
#app/plug/user.py - mixin for customising user model for this project from django.db import models from .perm import PermissionsMixin class UserMixin(PermissionsMixin, models.Model): class Meta: abstract = True @property def foo(self): return 'foo'
#accounts/user.py - define the user model class User(UserMixin, AbstractUser, modelpp.NiceNameMixin): """ Requirements: - Must be defined in models.py, due to the way settings.AUTH_USER_MODEL is defined """ objects = UserManager() username = None email = models.EmailField('email address', unique=True) is_staff = models.BooleanField( 'admin access', default=False, help_text='Designates whether the user can log into this admin site.', ) USERNAME_FIELD = 'email' REQUIRED_FIELDS = ('first_name',) def __str__(self): return self.get_full_name() class Meta: db_table = 'auth_user' #Other of custom methods ...
Change History (7)
comment:1 by , 17 months ago
comment:2 by , 17 months ago
Keywords: | make migrations removed |
---|---|
Resolution: | → invalid |
Status: | new → closed |
Hello!
First of all, please note that you are using a version of Django that is no longer supported (4.0.1).
Secondly, the error seems to be caused by either:
- a possible misconfiguration/bug in your code, or
- the result from squashing the migrations is somehow invalid.
For the first case, the recommended approach is to first ensure whether the error you are getting is a bug in Django itself, or not in your code. To do that, you can follow the bug reporting guide and seek help in the Django User channels to evaluate whether your problem is caused by a configuration issue.
For the second case, we would need a minimal example of two working migrations and their minimal models that, when squashed, would generate the invalid result. It's important to try to narrow the models and migrations to the bare minimum that triggers the issue, and that what's shared in the bug is fully functional. At the very least we'd need the migrations that were squashed down to the result you pasted in this report.
Closing this ticket as invalid for now, but if you find a concrete bug in Django itself or if you can provide a reproducer for the faulty squashed migration, please let us know and we'll analyze this further. Thanks!
comment:3 by , 5 months ago
Hi,
With regards to the 2 solutions:
- My code runs fine. There is not an error or bug in my code. Django generates this line:
bases=(core.djpp.permpp.PermissionsMixin_factory.<locals>.PermissionsMixin, models.Model, djpp.modelpp.NiceNameMixin),
Python will always fail the dot lookup of: .PermissionsMixin_factory.<locals>.PermissionsMixin
, note the <locals>
part.
- Happens event if I don't squash migrations, i.e. rebuild them all from scratch.
Everytime I generate migrations, I have to manually edit them so they are runnable. This is a bug in Django, and it's still present in v5.0.
It is repeatable every time, for years it has been doing it now. If you create a custom user model with the inheriance chain explained above, with the mixin shown above, you will get the same error.
I will make a repo of a minimal example project.
comment:4 by , 5 months ago
Resolution: | invalid |
---|---|
Status: | closed → new |
comment:5 by , 5 months ago
Version: | 4.0 → 5.0 |
---|
comment:6 by , 5 months ago
I created a minimal reproducible example:
https://github.com/mangelozzi/djangomigrationbug
- Check accounts/migrations/0001_initial.py
- You will see this line bases=(plug.permpp.PermissionsMixin_factory.<locals>.PermissionsMixin, models.Model, accounts.modelpp.NiceNameMixin),
- It has an error
<locals>
- It has an error
- Delete the above migration file
- Run python manage.py makemigrations
- The file above with the error will be created by the Django.
comment:7 by , 5 months ago
Resolution: | → invalid |
---|---|
Status: | new → closed |
Hi Micheal, thank you for providing more information here. Django does generate migrations that cannot be applied.
The generated migration file usually reflects what you have asked Django to do and it can only apply successfully if it is "valid".
Here User
is dependant on PermissionsMixin
(it is possible that PermissionsMixin
has some fields defined on it for example) and PermissionsMixin
is dependant on your locals as this is an output of a function ran at startup. IMO you can simplify your code to have a valid migration generated.
If you need any support with your project, please use one of our support channels such as the Django forum. It is recommended to go there first and confirm with others if there is agreement that this is either a bug in Django or something new Django should support.
Changing this line from:
to:
Make it work fine, since
PermissionsMixin
does not define any DB fields, is just run time behavior, but I thought this issue might highlight an underlying problem.