4

Like many others, I'm trying to configure my Django app to use the email as the username field. I have some existing user accounts that I migrated to a custom user model successfully, though right now the custom model is identical to the Django user model:

# accounts/models.py

from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
  pass

Anytime I try to set the USERNAME_FIELD to email, I get an error that the USERNAME_FIELD cannot be included in REQUIRED_FIELDS, as well as the error that it must be unique. (side note: email isn't required for the base User, so I don't understand the first error)

Is there any way to get this functionality while subclassing AbstractUser?

Or do I just need to subclass AbstractBaseUser and specify every field, in the manner of this example?

If it's the latter, how would I go about making sure I define the exact same fields as the Django User model? I don't want to lose the users I have or cause issues with mismatched fields.

It seems silly to have to go all the way to fully specifying a custom model just to use an email address as the username, so maybe I'm missing something here. If there was a way to ensure unique fields in the Django User model, I don't think this would be an issue.

dkhaupt
  • 2,220
  • 3
  • 23
  • 37

1 Answers1

4

As Django doc says:

If the changes you need are purely behavioral, and don’t require any change to what is stored in the database, you can create a proxy model based on User.

Since you want to substitute it with a custom User model:

For instance, on some sites it makes more sense to use an email address as your identification token instead of a username.

You'll need to implement your own User model by subclassing AbstractBaseUser. Here is a sample code with django premissions included as well:

    class User(AbstractBaseUser, PermissionsMixin):
    """
    A class implementing a fully featured User model with admin-compliant
    permissions.

    Email and password are required. Other fields are optional.
    """

    email = models.EmailField(
        _('Email Address'), unique=True,
        error_messages={
            'unique': _("A user with that email already exists."),
        }
    )
    username = models.CharField(
        _('Username'), max_length=30, unique=True, blank=True, null=True,
        help_text=_('30 characters or fewer. Letters, digits and _ only.'),
        validators=[
            validators.RegexValidator(
                r'^\w+$',
                _('Enter a valid username. This value may contain only '
                  'letters, numbers and _ character.'),
                'invalid'
            ),
        ],
        error_messages={
            'unique': _("The username is already taken."),
        }
    )
    is_staff = models.BooleanField(
        _('Staff Status'), default=False,
        help_text=_('Designates whether the user can log into this admin '
                    'site.')
    )
    is_active = models.BooleanField(
        _('Active'), default=True,
        help_text=_('Designates whether this user should be treated as '
                    'active. Unselect this instead of deleting accounts.')
    )
    date_joined = models.DateTimeField(_('Date Joined'), default=timezone.now)

    objects = UserManager()

    USERNAME_FIELD = 'email'

    class Meta(object):
        verbose_name = _('User')
        verbose_name_plural = _('Users')
        abstract = False

    def get_full_name(self):
        """
        Returns email instead of the fullname for the user.
        """
        return email_to_name(self.email)

    def get_short_name(self):
        """
        Returns the short name for the user.
        This function works the same as `get_full_name` method.
        It's just included for django built-in user comparability.
        """
        return self.get_full_name()

    def __str__(self):
        return self.email

    def email_user(self, subject, message, from_email=None, **kwargs):
        """
        Sends an email to this User.
        """
        send_mail(subject, message, from_email, [self.email], **kwargs)
Saeed
  • 661
  • 6
  • 12
  • Thanks- so since I have existing users, I need to exactly re-create the existing `User` model by extending `AbstractBaseUser`? I guess I'm a bit worried about copying in the same defintion as in the source (so much for DRY) and having conflict issues – dkhaupt Feb 20 '16 at 20:05
  • This ended up working- I had to remove the `abstract=True` from the `Meta` of the copied-in AbstractUser definition, but after that it was fine. Thanks! – dkhaupt Feb 20 '16 at 22:29
  • You're welcome. I used `abstract = False` in my code. Using the same class name won't cause any issue as long as you're careful when importing `User` class. – Saeed Feb 21 '16 at 11:46