Source code for purchasing.users.models

# -*- coding: utf-8 -*-
import random
from flask_security.utils import encrypt_password

from flask_login import AnonymousUserMixin
from flask_security import UserMixin, RoleMixin

from purchasing.database import Column, db, Model, ReferenceCol, SurrogatePK
from sqlalchemy.orm import backref

roles_users = db.Table(
    'roles_users',
    db.Column('user_id', db.Integer(), db.ForeignKey('users.id')),
    db.Column('role_id', db.Integer(), db.ForeignKey('roles.id'))
)

def rand_alphabet():
    ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    return encrypt_password(''.join(random.choice(ALPHABET) for i in range(16)))

[docs]class Role(SurrogatePK, RoleMixin, Model): '''Model to handle view-based permissions Attributes: id: primary key name: role name description: description of an individual role ''' __tablename__ = 'roles' name = Column(db.String(80), unique=True, nullable=False) description = Column(db.String(255), nullable=True) def __repr__(self): return '<Role({name})>'.format(name=self.name) def __unicode__(self): return self.name @classmethod
[docs] def query_factory(cls): '''Generates a query of all roles Returns: `sqla query`_ of all roles ''' return cls.query
@classmethod
[docs] def no_admins(cls): '''Generates a query of non-admin roles Returns: `sqla query`_ of roles without administrative access ''' return cls.query.filter(cls.name != 'superadmin')
@classmethod
[docs] def staff_factory(cls): '''Factory to return the staff role Returns: Role object with the name 'staff' ''' return cls.query.filter(cls.name == 'staff')
[docs]class User(UserMixin, SurrogatePK, Model): '''User model Attributes: id: primary key email: user email address first_name: first name of user last_name: last name of user active: whether user is currently active or not roles: relationship of user to role table department_id: foreign key of user's department department: relationship of user to department table ''' __tablename__ = 'users' email = Column(db.String(80), unique=True, nullable=False, index=True) first_name = Column(db.String(30), nullable=True) last_name = Column(db.String(30), nullable=True) active = Column(db.Boolean(), default=True) roles = db.relationship( 'Role', secondary=roles_users, backref=backref('users', lazy='dynamic'), ) department_id = ReferenceCol('department', ondelete='SET NULL', nullable=True) department = db.relationship( 'Department', backref=backref('users', lazy='dynamic'), foreign_keys=department_id, primaryjoin='User.department_id==Department.id' ) password = db.Column(db.String(255), nullable=False, default=rand_alphabet) confirmed_at = db.Column(db.DateTime) last_login_at = db.Column(db.DateTime) current_login_at = db.Column(db.DateTime) last_login_ip = db.Column(db.String(255)) current_login_ip = db.Column(db.String(255)) login_count = db.Column(db.Integer) def __repr__(self): return '<User({email!r})>'.format(email=self.email) def __unicode__(self): return self.email @property def role(self): if len(self.roles) > 0: return self.roles[0] return Role(name='') @property def full_name(self): '''Build full name of user Returns: concatenated string of first_name and last_name values ''' return "{0} {1}".format(self.first_name, self.last_name) @classmethod def department_user_factory(cls, department_id): return cls.query.filter( cls.department_id == department_id, db.func.lower(Department.name) != 'equal opportunity review commission' ) @classmethod def county_purchaser_factory(cls): return cls.query.filter( User.roles.any(Role.name == 'county') ) @classmethod def eorc_user_factory(cls): return cls.query.join( Department, User.department_id == Department.id ).filter( db.func.lower(Department.name) == 'equal opportunity review commission' ) @classmethod def get_subscriber_groups(cls, department_id): return [ cls.department_user_factory(department_id).all(), cls.county_purchaser_factory().all(), cls.eorc_user_factory().all() ]
[docs] def get_following(self): '''Generate user contract subscriptions Returns: list of ids for contracts followed by user ''' return [i.id for i in self.contracts_following]
[docs] def is_conductor(self): '''Check if user can access conductor application Returns: True if user's role is either conductor, admin, or superadmin, False otherwise ''' return any([ self.has_role('conductor'), self.has_role('admin'), self.has_role('superadmin') ])
[docs] def is_admin(self): '''Check if user can access admin applications Returns: True if user's role is admin or superadmin, False otherwise ''' return any([ self.has_role('admin'), self.has_role('superadmin') ])
[docs] def print_pretty_name(self): '''Generate long version text representation of user Returns: full_name if first_name and last_name exist, email otherwise ''' if self.first_name and self.last_name: return self.full_name else: return self.email
[docs] def print_pretty_first_name(self): '''Generate abbreviated text representation of user Returns: first_name if first_name exists, `localpart <https://en.wikipedia.org/wiki/Email_address#Local_part>`_ otherwise ''' if self.first_name: return self.first_name else: return self.email.split('@')[0]
@classmethod
[docs] def conductor_users_query(cls): '''Query users with access to conductor Returns: list of users with ``is_conductor`` value of True ''' return [i for i in cls.query.all() if i.is_conductor()]
[docs]class Department(SurrogatePK, Model): '''Department model Attributes: name: Name of department ''' __tablename__ = 'department' name = Column(db.String(255), nullable=False, unique=True) def __unicode__(self): return self.name @classmethod
[docs] def query_factory(cls): '''Generate a department query factory. Returns: Department query with new users filtered out ''' return cls.query.filter(cls.name != 'New User')
@classmethod
[docs] def get_dept(cls, dept_name): '''Query Department by name. Arguments: dept_name: name used for query Returns: an instance of Department ''' return cls.query.filter(db.func.lower(cls.name) == dept_name.lower()).first()
@classmethod
[docs] def choices(cls, blank=False): '''Query available departments by name and id. Arguments: blank: adds none choice to list when True, only returns Departments when False. Defaults to False. Returns: list of (department id, department name) tuples ''' departments = [(i.id, i.name) for i in cls.query_factory().all()] if blank: departments = [(None, '-----')] + departments return departments
[docs]class AnonymousUser(AnonymousUserMixin): '''Custom mixin for handling anonymous (non-logged-in) users Attributes: roles: List of a single :py:class:`~purchasing.user.models.Role` object with name set to 'anonymous' department: :py:class:`~purchasing.user.models.Department` object with name set to 'anonymous' id: Defaults to -1 See Also: ``AnonymousUser`` subclasses the `flask_login anonymous user mixin <https://flask-login.readthedocs.org/en/latest/#anonymous-users>`_, which contains a number of class and instance methods around determining if users are currently logged in. ''' roles = [Role(name='anonymous')] department = Department(name='anonymous') id = -1 def __init__(self, *args, **kwargs): super(AnonymousUser, self).__init__(*args, **kwargs) def is_conductor(self): return False def is_admin(self): return False