class User(AbstractBaseUser, PermissionsMixin): """Override of the Django class to handle user authentication.""" # ==Identification== # To allow for multiple users with the same username, users are publicly identified with username#identifier. # Internally, only the couple username + identifier must be unique, but either can be duplicate on their own. username = models.CharField(max_length=150, db_column="user_username") # The identifier is stored as a string of digits including trailing zeroes. # It is assigned at creation and modification of a user's nick using the first value that doesn't conflict. identifier = models.CharField(max_length=settings.IDENTIFIER_DIGITS)
email = models.EmailField(unique=True, db_column="user_email")
objects = UserManagerOverride() # Override the default manager to customise user creation.
is_superuser = models.BooleanField(default=False) is_staff = models.BooleanField(default=False) is_active = models.BooleanField(default=True)
USERNAME_FIELD = "email" EMAIL_FIELD = "email" REQUIRED_FIELDS = ["username"]
class Meta: unique_together = ("username", "identifier")class UserManagerOverride(BaseUserManager): """Override of the default Django user manager."""
def _create_user(self, email, username, password, **extra_fields): """Override the parent function to create a user and save it in the database.""" if not email: raise ValueError('An email is required.')
# Prepare the data to be stored into the database. email = self.normalize_email(email) username = self.model.normalize_username(username) identifier = self.generate_identifier(username)
# Make the user instance. user = self.model(username=username, email=email, identifier=identifier, **extra_fields)
# Add the password after encrypting. user.set_password(password)
try: # Save to database. user.save(using=self._db) except Exception: # If there was an error, scratch the user row to preserve integrity with User and UserData primary keys. user.delete() raise else: # Create a UserData record for this user. # Thanks to the try-else, we only do this if the code raised no exceptions. UserData.objects.create(user=user)
return user
def create_user(self, email, username, password=None, **extra_fields): """Override parent function to create a regular user account.""" extra_fields.setdefault('is_staff', False) extra_fields.setdefault('is_superuser', False) return self._create_user(email, username, password, **extra_fields)
def create_superuser(self, email, username, password, **extra_fields): """Override parent function to create a superuser account.""" extra_fields.setdefault('is_staff', True) extra_fields.setdefault('is_superuser', True)
if extra_fields.get('is_staff') is not True: raise ValueError('Superuser must have is_staff=True.') if extra_fields.get('is_superuser') is not True: raise ValueError('Superuser must have is_superuser=True.')
return self._create_user(username, email, password, **extra_fields)class EmailBackend(ModelBackend): """Allow authentication using email instead of username.""" def authenticate(self, username=None, password=None, **kwargs): user_model = get_user_model() try: user = user_model.objects.get(email=username) except user_model.DoesNotExist: return None else: if user.check_password(password): return user return None