Cool Breeze
Cool Breeze

Reputation: 910

Django: set default value for new model field which has unique=True

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?

Upvotes: 1

Views: 1700

Answers (3)

NixonSparrow
NixonSparrow

Reputation: 6378

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()

Upvotes: 1

kaveh
kaveh

Reputation: 2146

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.

Upvotes: 3

Pratha
Pratha

Reputation: 9

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

Upvotes: 0

Related Questions