Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django - Allow duplicate usernames

I'm working on a project in django which calls for having separate groups of users in their own username namespace.

So for example, I might have multiple "organizations", and username should only have to be unique within that organization.

I know I can do this by using another model that contains a username/organization id, but that still leaves this useless (and required) field on the defualt django auth User that I would have to populate with something.

I've already written by own auth backend that authenticates a user against LDAP. However, as I mentioned before, I am still stuck with the problem of how to populate / ignore the username field on the default django user.

Is there a way to drop the uniqueness constraint for the username for Django auth users?

like image 390
TM. Avatar asked Jan 08 '10 15:01

TM.


4 Answers

What you can do is extend the User model. For the User table, generate a username (e.g. A_123, A_345) that will not be displayed at all in the site.

Then create a new model that extends User.

class AppUser(User):
    username = models.CharField...
    organization = models.CharField...

You then create a custom authentication backend that use AppUser instead of the User object.

like image 71
dannyroa Avatar answered Nov 09 '22 07:11

dannyroa


I also suffered with this problem. I was doing a project where I had to use email and mobile no. as login fields but none of them should be unique because their were different types of users and a user can have more than one user entity and also the project required only one auth user table (Hectic right!).

So I extended AbstractBaseUser class where I could change the USERNAME_FIELD attribute. Here's how :-

from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import PermissionsMixin

# Custom model for User
class User(AbstractBaseUser, PermissionsMixin):

    first_name = models.CharField(max_length=100, blank=False)
    last_name = models.CharField(max_length=100, blank=True)
    password = models.CharField(max_length=255, blank=False)
    email = models.EmailField(max_length=255, blank=False)
    mobile = models.CharField(max_length=12)
    user_type = models.ForeignKey(UserTypes, on_delete=models.DO_NOTHING)
    is_approved = models.BooleanField(default=False)

    objects = UserManager()
    # Here's the Catch
    USERNAME_FIELD = 'id'

    def get_full_name(self):
        '''
        Returns the first_name plus the last_name, with a space in between.
        '''
        full_name = '%s %s' % (self.first_name, self.last_name)
        return full_name.strip()

    def get_short_name(self):
        '''
        Returns the short name for the user.
        '''
        return self.first_name

    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)

    class Meta:
        db_table = 'user'

Yes exactly, surprised? USERNAME_FIELD should be a unique field that's the constraint of this attribute. I couldn't use email or mobile no. as unique field.

Then I created a custom manager to remove the username field (reference = https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html#abstractbaseuser)

from django.contrib.auth.base_user import BaseUserManager


class UserManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, email, password, **extra_fields):
        """
        Creates and saves a User with the given email and password.
        """
        if not email:
            raise ValueError('The given email must be set')
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email, password=None, **extra_fields):
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(email, password, **extra_fields)

    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self._create_user(email, password, **extra_fields)

This will do the trick.

like image 22
Srijan Anand Avatar answered Nov 09 '22 06:11

Srijan Anand


I'm not sure if this is exactly what you're looking for, but I think you could use a hack similar to what is in this answer.

The following code works, as long as it is in a place that gets executed when Django loads your models.

from django.contrib.auth.models import User

User._meta.get_field('username')._unique = False

Note that this won't change the database unique constraint on the auth_user table if it has been already been created. Therefore you need to make this change before you run syncdb. Alternatively, if you don't want to recreate your auth_user table, you could make this change and then manually alter the database table to remove the constraint.

like image 41
Monika Sulik Avatar answered Nov 09 '22 06:11

Monika Sulik


I have not personally been required to find a solution to this, but one way to tackle this (from an SAAS perspective) would be to prefix the username with an organizational identifier (presuming unique organizations). For example: subdomain.yoursite.com would equate to a user with the username: subdomain_username. You would just have to code some business logic on login to a subdomain to tack that onto the username.

like image 36
DrBloodmoney Avatar answered Nov 09 '22 07:11

DrBloodmoney