# Copyright (c) 2013 - 2014 by pyramid_fullauth authors and contributors <see AUTHORS file>
#
# This module is part of pyramid_fullauth and is released under
# the MIT License (MIT): http://opensource.org/licenses/MIT
"""Models needed for registration, and user servicing."""
import sys
import uuid
from sqlalchemy import (
Column, Unicode, String, Integer, Boolean, Sequence, DateTime,
Table, ForeignKey, UniqueConstraint
)
from sqlalchemy.sql import func
from sqlalchemy.orm import validates, relationship
from sqlalchemy.orm.session import object_session
from sqlalchemy.orm.util import has_identity
from pyramid_basemodel import Base
from pyramid_fullauth import exceptions
from pyramid_fullauth.models.mixins import UserPasswordMixin, UserEmailMixin
from datetime import datetime
[docs]class User(UserPasswordMixin, UserEmailMixin, Base):
"""User object."""
__tablename__ = 'users'
id = Column(Integer, Sequence(__tablename__ + '_sq'), primary_key=True)
username = Column(Unicode(32), unique=True, nullable=True)
firstname = Column(Unicode(100), nullable=True)
lastname = Column(Unicode(100), nullable=True)
activate_key = Column(String(255), default=lambda: str(uuid.uuid4()), unique=True)
address_ip = Column(String(15), nullable=False)
registered_at = Column(DateTime, default=func.now(), nullable=False)
logged_at = Column(DateTime, default=func.now(), nullable=False)
activated_at = Column(DateTime, nullable=True)
deactivated_at = Column(DateTime, nullable=True)
deleted_at = Column(DateTime, nullable=True)
is_admin = Column(Boolean, default=False, nullable=False)
@property
def is_active(self):
"""
Check if user is active.
:returns: Returns False if user account is not active (or deleted).
:rtype: bool
"""
return not (self.deactivated_at or self.deleted_at or self.activate_key) and bool(self.activated_at)
@is_active.setter
[docs] def is_active(self, value):
"""
Set user as active/inactive.
:param bood value:
True - removes deactivated_at, deleted_at, activate_key and set activated_at to datetime now
False - set deactivated_at to now and activated_at to None
"""
# Allow to modify this only if object is in the persistent state to prevent "default values" errors/bugs.
# http://stackoverflow.com/questions/3885601/sqlalchemy-get-object-instance-state
if object_session(self) is not None and has_identity(self):
if value:
self.deactivated_at = None
self.deleted_at = None
self.activate_key = None
self.activated_at = datetime.now()
else:
self.deactivated_at = datetime.now()
self.activated_at = None
else:
raise AttributeError('User has to be in the persistent state - stored in the DB')
[docs] def provider_id(self, provider):
"""
Return provider identification for give user.
:param str provider: provider name
:returns: provider identification
:rtype: str
"""
for user_provider in self.providers:
if user_provider.provider == provider:
return user_provider.provider_id
def __repr__(self):
"""Object representation."""
return "<User ('{0}', '{1}')>".format(self.id, str(self))
def __unicode__(self):
"""Unicode cast rules."""
if self.username:
return self.username
elif self.email:
return self.email.split('@')[0] + '@...'
else:
return str(self.id)
def __str__(self): # pragma: no cover
"""String cast rules."""
if sys.version[0] == '3':
return self.__unicode__()
else:
return self.__unicode__().encode('utf-8')
@validates('is_admin')
[docs] def validate_is_admin(self, key, value):
"""
Validate is_admin value, we forbid the deletion of the last superadmin.
.. note::
More about simple validators: http://docs.sqlalchemy.org/en/latest/orm/mapper_config.html#simple-validators
:raises AttributeError: Information about an error
"""
if self.is_admin and not value:
admin_counter = object_session(self).query(User).filter(
User.is_admin, User.deleted_at.is_(None)
).count()
if admin_counter and admin_counter <= 1:
raise AttributeError('Can\'t delete last superadmin!')
return value
[docs] def delete(self):
"""
Perform soft delete action. along with checking if it's super admin, or not.
:rises pyramid_fullauth.exceptions.DeleteException: if you try to delete last super admin.
.. note::
You should use this method to delete users
"""
if self.is_admin:
admin_counter = object_session(self).query(User).filter(
User.is_admin, User.deleted_at.is_(None)
).count()
if admin_counter and admin_counter <= 1:
raise exceptions.DeleteException('Can\'t delete last superadmin!')
self.deleted_at = datetime.now()
[docs]class Group(Base):
"""User group object."""
__tablename__ = 'groups'
id = Column(Integer, Sequence(__tablename__ + '_sq'), primary_key=True)
name = Column(Unicode(100), unique=True, nullable=False)
#: Relation to User object
users = relationship(User, secondary=lambda: user_group, backref='groups')
[docs]class AuthenticationProvider(Base):
"""Model to store authentication methods for different providers."""
__tablename__ = 'user_authentication_provider'
__table_args__ = (
UniqueConstraint('provider', 'provider_id', name='user_authentication_methods_provider_id'),
)
user_id = Column(Integer, ForeignKey(User.id), primary_key=True)
provider = Column(Unicode(15), primary_key=True)
provider_id = Column(String(255), nullable=False)
user = relationship(User, backref='providers')
#: Association table between User and Group models.
user_group = Table(
'users_groups',
Base.metadata,
Column('user_id', Integer, ForeignKey(User.id), primary_key=True),
Column('group_id', Integer, ForeignKey(Group.id), primary_key=True)
)