Learn more  » Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Bower components Debian packages RPM packages NuGet packages

nickfrez / unb-uuiduser   python

Repository URL to install this package:

/ models.py

import re
import uuid

from django.contrib.auth import models as auth_models
from django.contrib.auth.tokens import default_token_generator
from django.core import validators
from django.db import models
from django.utils import timezone

from .fields import NullCharField


# TODO(nick): We'll have to include migrations in the UUIDUser app as well,
#   since it's not entirely straight-forward to migrate.


class DefaultUserQuerySet(models.QuerySet):
  def _filter_or_exclude(self, mapper, *args, **kwargs):
    """Make username lookups case-insensitive by default.

    Usernames are stored as case-sensitive strings so they may be displayed as
    the user entered them.

    Note: This does not work for more complex queries, like: related__username.
    """
    if 'username' in kwargs:
      kwargs['username__iexact'] = kwargs['username']
      del kwargs['username']
    return super(DefaultUserQuerySet, self)._filter_or_exclude(
      mapper, *args, **kwargs)

  def username(self, username):
    return self.get(username__iexact=username)

  def active(self):
    return self.filter(is_active=True)

  def staff(self):
    return self.filter(is_staff=True)

  def admin(self):
    return self.filter(is_superuser=True)


class DefaultUserManager(auth_models.BaseUserManager.from_queryset(DefaultUserQuerySet)):
  """Default User manager that implements compliant create methods."""

  # .. NOTE:: We are using the from_queryset() function to dynamically
  #           generate a manager class, so we need to inherit from the
  #           generated class to make it importable in migrations.
  #
  #           https://docs.djangoproject.com/en/1.9/topics/migrations/#model-managers
  #
  # TODO(nick): Does this manager absolutely need to be available in
  #             migrations?  According to the `Historical models`_ section in
  #             Django docs, it appears as though allowing this manager in
  #             migrations may become a maintenence nightmare if we have to
  #             "keep
  #
  #                 References to ... and model manager declarations with
  #                 managers having use_in_migrations = True are serialized in
  #                 migrations, so the functions and classes will need to be
  #                 kept around for as long as there is a migration referencing
  #                 them.
  #
  # .. _`Historical models`: https://docs.djangoproject.com/en/1.9/topics/migrations/#historical-models
  #
  use_in_migrations = True

  def create(self, password=None, username=None, **extra):
    # Don't let the uuid field be specified in create calls.
    try:
      del extra['uuid']
    except KeyError:
      pass

    user = self.model(**extra)

    if password:
      user.set_password(password)
    else:
      user.set_unusable_password()

    if username:
      user.set_username(username)

    user.save(using=self._db)

    return user

  def create_user(self, email=None, password=None, username=None, **extra):
    """Provide an auth.User compliant create_user api."""
    return self.create(password=password, username=username, **extra)

  def create_superuser(self, email=None, password=None, username=None,
                       **extra):
    """Provide an auth.User compliant create_superuser api."""
    return self.create(password=password, username=username, is_staff=True,
                       is_superuser=True, **extra)


class UUIDUser(auth_models.PermissionsMixin, auth_models.AbstractBaseUser):
  """Representation of a user (account) identified by a UUID.

  Attrs:
    username: Users should be able to set a (unique) username which they can
      use for login, display name, @ mentions, etc.
      Usernames are stored, and should be displayed, as they are entered
      (case-sensitive).  However, username queries should **always** be
      case-insensitive!

    uuid: Since usernames are optional and email is handled by another app, the
      uuid field simply provides a unique identifier for users.  It is
      auto-generated at user account creation time.  This puts the onus of
      preventing duplicate accounts on some undefined external process.

  Django auth.User Differences:

    first_name & last_name
      The `django.contrib.auth.User` model provides `first_name` and
      `last_name` fields.


      The standard contrib.auth.User first_name, last_name and email fields are
      deprecated, but can't be removed since there is, or may be, 3rd party
      code that depends on thier existence.

  """

  class Meta:
    abstract = True
    verbose_name = 'user'
    verbose_name_plural = 'users'


  # Model Managers/QuerySets
  # =========================

  objects = DefaultUserManager.from_queryset(DefaultUserQuerySet)()


  # Constants
  # ==========

  UNIQUE_IDENTIFIER_FIELD = 'uuid'

  # NOTE: A username is required for admin access!
  #
  # Django requires a unique username field for much of their admin
  # functionality.  Unfortunately setting this to our unique field (``uuid``)
  # breaks most of the default Django functionality (unless you want to type in
  # UUIDs all over the place).
  USERNAME_FIELD = 'username'

  # Additional fields required in createsuperuser command.
  REQUIRED_FIELDS = []

  USERNAME_VALIDATOR = validators.RegexValidator(
    re.compile(
      # Match 2 character usernames
      "("
      "[a-zA-Z]"       # First char must be a letter
      "[a-zA-Z0-9]"    # Last char must be a letter or number
      ")"

      "|"              # or

      # Match 3+ character usernames
      "("
      "[a-zA-Z]"       # First char must be a letter
      "(?![-_'.]{2})"  # Username must not contain repeated punctuation
      "[\\w.'-]+"      # First char may be followed by any alpha-numberic
                       # character (including - . ' _)
      "[a-zA-Z0-9]"    # Last char must be a letter or number
      ")"
    ),
    code='invalid')


  # Model Fields
  # =============

  uuid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)

  username = NullCharField(max_length=255,
                           unique=True,
                           blank=True,
                           null=True,
                           validators=[USERNAME_VALIDATOR])

  name = models.CharField(max_length=255, default='', blank=True)

  short_name = models.CharField(max_length=255, default='', blank=True)

  first_name = models.CharField(max_length=255, default='', blank=True)

  last_name = models.CharField(max_length=255, default='', blank=True)

  is_staff = models.BooleanField(default=False,
                                 help_text='Can log in to admin.')
  is_active = models.BooleanField(default=True)

  date_joined = models.DateTimeField(default=timezone.now)

  created_at = models.DateTimeField(auto_now_add=True)

  # Fields provided by contrib.auth.models.AbstractBaseUser
  # -------------------------------------------------------

  # password = models.CharField(_('password'), max_length=128)
  # last_login = models.DateTimeField(_('last login'), blank=True, null=True)

  # Fields provided by contrib.auth.models.PermissionsMixin
  # -------------------------------------------------------

  # is_superuser = models.BooleanField(
  #   default=False,
  #   verbose_name='superuser status',
  #   help_text=('Designates that this user has all permissions without '
  #              'explicitly assigning them.'))
  # groups = models.ManyToManyField(
  #   Group,
  #   blank=True,
  #   related_name="user_set",
  #   related_query_name="user",
  #   verbose_name='groups',
  #   help_text=('The groups this user belongs to. A user will get all '
  #              'permissions granted to each of their groups.'))
  # user_permissions = models.ManyToManyField(
  #   Permission,
  #   blank=True,
  #   related_name="user_set",
  #   related_query_name="user",
  #   verbose_name='user permissions',
  #   help_text='Specific permissions for this user.')


  # Instance Methods and Properties
  # ================================

  def __str__(self):
    return str(self.get_username())

  @property
  def email(self):
    """A psudo-field representing the User's primary email as a string.

    This psudo-field is provided to mimic the api exposed by the
    contrib.auth.User model.

    NOTE: It is not required that sub-classes implement this.  However, 3rd
    party code (and even possibly parts of Django) may assume that a settable
    field named ``email`` exists on the User model.

    Returns the primary email for the User as a string, or the empty string if
    a primary email does not exist.
    """
    return ''

  @email.setter
  def email(self, address):
    """Provide a setter for the ``email`` psudo-field.

    This setter should add a "primary" email address to the User.  It is left
    up to the implementation to determine what "primary" means in its context.

    NOTE: It is not required that sub-classes implement this.  However, 3rd
    party code (and even possibly parts of Django) may assume that a settable
    field named ``email`` exists on the User model.

    Args:

      address(str): Email address as a string.
    """
    pass

  def set_username(self, username):
    """Set the username, taking care to normalize it first.

    TODO(nick): Ultimately we should store usernames as case-sensitive strings
      and simply use case-insensitive logic when dealing with them.  This
      provides a better UX, as we are able to display a user's username to them
      exactly as they have entered it.
      However, making this transparent and ubiquitous is non-trivial.
      Therefore, this functionality will be reserved for a future version of
      UUIDUser.  For now, just set usernames with this ``set_username`` method
      and query for them using ``User.objects.username('username')``.
    """
    username = username.lower()
    self.username = username

  def get_full_name(self):
    return self.name

  def get_short_name(self):
    return self.short_name

  def email_user(self, subject, message, from_email=None, **kwargs):
    return False

  def get_password_reset_token(self):
    return default_token_generator.make_token(self)

  def check_password_reset_token(self, token):
    return default_token_generator.check_token(self, token)

  @classmethod
  def get_by_email(cls, email, primary_only=False):
    """Return a single User instance, given an email address.

    Args:

      email(str): An email address belonging to the user.
      primary_only(bool): Whether to include matches of non-primary addresses.

    Raises:

      UUIDUser.DoesNotExist: If the User was not found.

    Returns an instance of UUIDUser if the User was found.
    """
    raise UUIDUser.DoesNotExist