4

I'm migrating my app from Django 1.4 to Django 1.7. I have already upgraded all third party libraries and Django itself. No errors and warnings on running manage.py check or when using manage.py runserver'. Now I want to move to a custom user model. I have created the model and it's a direct copy of the User model from django.contrib.auth.models.User with one difference - I have created my own PermissionMixin base because with the default one the reverse relationships were clashing between the default User model and my custom User model.

class PermissionsMixin(models.Model):
    is_superuser = models.BooleanField('superuser status', default=False)
    groups = models.ManyToManyField(
        Group, verbose_name='custom_groups', blank=True,
        related_name="custom_user_set", related_query_name="user")
    user_permissions = models.ManyToManyField(
        Permission, verbose_name='user permissions', blank=True,
        related_name="custom_user_set", related_query_name="user")

    class Meta:
        abstract = True

    def get_group_permissions(self, obj=None):
        permissions = set()
        for backend in auth.get_backends():
            if hasattr(backend, "get_group_permissions"):
                permissions.update(backend.get_group_permissions(self, obj))
        return permissions

    def get_all_permissions(self, obj=None):
        return _user_get_all_permissions(self, obj)

    def has_perm(self, perm, obj=None):

        # Active superusers have all permissions.
        if self.is_active and self.is_superuser:
            return True

        # Otherwise we need to check the backends.
        return _user_has_perm(self, perm, obj)

    def has_perms(self, perm_list, obj=None):
        for perm in perm_list:
            if not self.has_perm(perm, obj):
                return False
        return True

    def has_module_perms(self, app_label):
        # Active superusers have all permissions.
        if self.is_active and self.is_superuser:
            return True

        return _user_has_module_perms(self, app_label)


class CustomUser(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField('email address', max_length=254, unique=True)
    first_name = models.CharField('first name', max_length=30, blank=True)
    last_name = models.CharField('last name', max_length=30, blank=True)
    is_staff = models.BooleanField('staff status', default=False)
    is_active = models.BooleanField('active', default=True)
    date_joined = models.DateTimeField('date joined', default=timezone.now)

    USERNAME_FIELD = 'email'

Until that point everything is fine - the migration creates the required tables in the database and I can modify the Custom user model in admin. Now I want to copy all the data from auth.User to my users.CustomUser model with a datamigration. The below code reflects what I would like to do:

from django.db import migrations, transaction


@transaction.atomic
def copy_old_users(apps, schema_editor):
    User = apps.get_model("auth", "User")
    CustomUser = apps.get_model("users", "CustomUser")

    fields = ['id', 'username', 'email', 'first_name', 'last_name',
              'is_staff', 'is_active', 'date_joined', 'is_superuser',
              'last_login']

    for user in User.objects.all():
        custom_user = CustomUser()
        for field in fields:
            setattr(custom_user, field, getattr(user, field))

        custom_user.save()

        custom_user.groups.add(*user.groups.all())
        custom_user.user_permissions.add(*user.user_permissions.all())

@transaction.atomic
def remove_new_users(apps, schema_editor):
    CustomUser = apps.get_model("users", "CustomUser")
    CustomUser.objects.all().delete()


class Migration(migrations.Migration):

    dependencies = [
        ('auth', '0003_auto_20150128_1715'),
        ('users', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(copy_old_users, remove_new_users),
    ]

I am using MySQL. The problem here is user.user_permissions.all() and user.groups.all() are always empty. I have tried the same in the django shell - empty. In admin - the groups aren't properly attached and the user_permissions are. I have checked in the database - this should not be empty. Further investigation showed that the following code retrieves the required objects:
User._meta.get_field('groups').rel.through.objects.filter(user_id=user.id)

and it's the same with user_permissions. I can iterate through the objects list from the above query and access the Group object to use it in my migration. This is all well and working.

Questions are: why doesn't the standard way work? Am I accessing the objects in some bad way? Have I missed something in data migration? Should I import(?!) some specific dependency or retrieve the Permission and Group models in some special way?

Ania Warzecha
  • 1,796
  • 13
  • 26
  • Can you post your custom user model? – Kinsa Feb 23 '15 at 22:43
  • @jbergantine I have edited the question with the user model. – Ania Warzecha Feb 25 '15 at 12:45
  • 1
    I suggest you to bring this up to Django bug tracker. – Jorge Leitao Feb 26 '15 at 14:06
  • Do you need to introduce the [`has_perm()` and `has_perms()` methods](https://docs.djangoproject.com/en/1.7/ref/contrib/auth/#django.contrib.auth.models.User.has_perm) perhaps? I've done a custom user model that defined those methods and just implemented `return True` for each one. – Kinsa Mar 06 '15 at 19:05
  • This might also be useful: http://www.caktusgroup.com/blog/2013/08/07/migrating-custom-user-model-django/#comment-1056203268 – Kinsa Mar 06 '15 at 19:08
  • A gist of `has_perm()` and `has_perms()` that I'm using: https://gist.github.com/jbergantine/cb3c0e1cd4c2df0b5587 – Kinsa Mar 06 '15 at 19:11
  • @jbergantine I think you don't understand where the problem lies. I want to move permissions between user models, not mock them on the new user model. I am also able to do this successfully. As far as I have debugged the problem was that the manager on the groups and permissions was set to the new models, not the old ones, for the old User model. – Ania Warzecha Mar 07 '15 at 20:36

1 Answers1

0

I don't have enough reputation to comment, but if it's a one-time migration, I'd recommend doing a json dump of your auth.group and auth.permission models:

./manage.py dumpdata --indent=2 auth.group auth.permission > groups_and_perms.json

Then you can load them in again with:

./manage.py loaddata groups_and_perms.json

Also, groups and permissions would be ManyToMany relations, wouldn't they be? If that's the case, try using .save_m2m() in your copy_old_users() function:

custom_user.save()

custom_user.groups.add(*user.groups.all())
custom_user.user_permissions.add(*user.user_permissions.all())

custom_user.save_m2m()
Franey
  • 4,164
  • 2
  • 16
  • 18
  • I am not really sure what are you suggesting. I have defined the "other way round" solution in my question that is easier to implement than your solution and it does work. Also you don't need to call `save_m2m` if you are using `.add` - it does the saving for you. – Ania Warzecha Feb 26 '15 at 18:10