Home > Software design >  Django: set default value for new model field which has unique=True
Django: set default value for new model field which has unique=True

Time:10-20

There is a custom user model which inherits AbstractUser in Django.

The model has username = None, below is the model:

class User(AbstractUser):
    username = None
    email = models.EmailField(_("Email address"), unique=True)

    USERNAME_FIELD = "email"

I want to remove username = None so that we can save usernames as well.

But the issues is we have various users in the database.

and when I remove the username = None and try to migrate, I get the prompt:

It is impossible to add a non-nullable field 'username' to user without specifying a default. This is because the database needs something to populate existing rows. Please select a fix:

  1. Provide a one-off default now (will be set on all existing rows with a null value for this column)
  2. Quit and manually define a default value in models.py.

I don't want to override the username field of AbstractUser class.

AbstractUser > username:

username_validator = UnicodeUsernameValidator()

    username = models.CharField(
        _("username"),
        max_length=150,
        unique=True,
        help_text=_(
            "Required. 150 characters or fewer. Letters, digits and @/./ /-/_ only."
        ),
        validators=[username_validator],
        error_messages={
            "unique": _("A user with that username already exists."),
        },
    )

I want username to take value of email if a different value is not provided.

How can I provide the default value?

CodePudding user response:

You can use a custom migration. When creating your migration, provide a default value, then edit the generated migration with something like this:

import django.contrib.auth.models
from django.db import migrations, models
import users.models


def forwards_func(apps, schema_editor):
    User = apps.get_model("users", "User")
    User.objects.update(username=models.F("email"))


def reverse_func(apps, schema_editor):
    pass


class Migration(migrations.Migration):

    dependencies = [
        ('users', '0001_initial'),
    ]

    operations = [
        migrations.AlterModelManagers(
            name='user',
            managers=[
                ('manager', users.models.UserManager()),
                ('objects', django.contrib.auth.models.UserManager()),
            ],
        ),
        migrations.AddField(
            model_name='user',
            name='username',
            field=models.EmailField(max_length=254, unique=True, null=True),
            preserve_default=False,
        ),
        migrations.RunPython(forwards_func, reverse_func),
        migrations.AlterField(
            model_name='user',
            name='username',
            field=models.EmailField(max_length=254, unique=True),
        ),
    ]

Here's the explanation of the changes:

  • First, make the column nullable by adding null=True to the new username field.
  • Update the username column of existing users via RunPython action. In the forward_func, we use the value of email column for username. Reverse function can be empty because the column will be removed.
  • Now you can make the field required by setting null to its default value, False, via AlterField.

CodePudding user response:

You can use pre_save singal like that:

# signals.py

from django.db.models.signals import pre_save
from django.dispatch import receiver

@receiver(pre_save, sender=User)
def fill_username(sender, instance, *args, **kwargs):
    if not instance.username:
        instance.username = instance.email

# apps.py

class UserConfig(AppConfig):
    ...

    def ready(self):
        import users.signals

In this way it will always check if username is entered. In case it's empty it will copy value from email.

For existing Users you might need to call save() manually or with for loop in shell:

$ python manage.py shell

>> from users.models import User
>> for user in User.objects.all():
...    user.username = user.email
...    user.save()

CodePudding user response:

You can use migration for older values and write the setter for username in your Serializer for the new entries

  • Related